initial commit
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
name: Build and Test
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- fix-*
|
||||||
|
- feat-*
|
||||||
|
- update-*
|
||||||
|
paths:
|
||||||
|
- '**.go'
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- synchronize
|
||||||
|
- reopened
|
||||||
|
paths:
|
||||||
|
- '**.go'
|
||||||
|
- go.mod
|
||||||
|
- go.sum
|
||||||
|
jobs:
|
||||||
|
build-test-publish-coverage:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.22'
|
||||||
|
- run: go build
|
||||||
|
- run: |
|
||||||
|
docker compose -p coredns-netbox-plugin-dns -f ${{ github.workspace }}/.testing/docker-compose.yml up -d && \
|
||||||
|
until [[ "`docker inspect -f {{.State.Health.Status}} coredns-netbox-plugin-dns-netbox-1`" == "healthy" ]]; do
|
||||||
|
echo "Waiting for Netbox to come online..."
|
||||||
|
sleep 5
|
||||||
|
done && \
|
||||||
|
go run ${{ github.workspace }}/.testing/init/init.go
|
||||||
|
- run: go test -coverprofile='coverage.out'
|
||||||
|
- uses: sonarsource/sonarcloud-github-action@master
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
with:
|
||||||
|
args: >
|
||||||
|
-Dsonar.organization=doubleu-labs
|
||||||
|
-Dsonar.projectKey=doubleu-labs_coredns-netbox-plugin-dns
|
||||||
|
-Dsonar.go.coverage.reportPaths=coverage.out
|
||||||
|
-Dsonar.verbose=true
|
||||||
|
-Dsonar.sources=.
|
||||||
|
-Dsonar.exclusions=**/*_test.go,.testing/*
|
||||||
|
-Dsonar.tests=.
|
||||||
|
-Dsonar.test.inclusions=**/*_test.go
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
coverage.out
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
PLUGINS = [
|
||||||
|
'netbox_dns',
|
||||||
|
]
|
||||||
|
|
||||||
|
PLUGINS_CONFIG = {
|
||||||
|
'netbox_dns': {
|
||||||
|
'feature_ipam_coupling': True,
|
||||||
|
'tolerate_underscores_in_hostnames': True,
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
services:
|
||||||
|
netbox:
|
||||||
|
image: localhost/netbox-plugin-dns:latest
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./netbox.Containerfile
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
- redis
|
||||||
|
- redis-cache
|
||||||
|
env_file: env/netbox.env
|
||||||
|
user: 'unit:root'
|
||||||
|
healthcheck:
|
||||||
|
start_period: 60s
|
||||||
|
timeout: 3s
|
||||||
|
interval: 15s
|
||||||
|
test: "curl -f http://localhost:8080/api/ || exit 1"
|
||||||
|
ports:
|
||||||
|
- "9999:8080"
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: docker.io/library/postgres:16
|
||||||
|
env_file: env/postgres.env
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: docker.io/library/redis:7
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- redis-server --appendonly yes --requirepass $$REDIS_PASSWORD
|
||||||
|
env_file: env/redis.env
|
||||||
|
|
||||||
|
redis-cache:
|
||||||
|
image: docker.io/library/redis:7
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- redis-server --requirepass $$REDIS_PASSWORD
|
||||||
|
env_file: env/redis-cache.env
|
||||||
Vendored
+34
@@ -0,0 +1,34 @@
|
|||||||
|
CORS_ORIGIN_ALLOW_ALL=True
|
||||||
|
DB_HOST=postgres
|
||||||
|
DB_NAME=netbox
|
||||||
|
DB_PASSWORD=J5brHrAXFLQSif0K
|
||||||
|
DB_USER=netbox
|
||||||
|
EMAIL_FROM=netbox@bar.com
|
||||||
|
EMAIL_PASSWORD=
|
||||||
|
EMAIL_PORT=25
|
||||||
|
EMAIL_SERVER=localhost
|
||||||
|
EMAIL_SSL_CERTFILE=
|
||||||
|
EMAIL_SSL_KEYFILE=
|
||||||
|
EMAIL_TIMEOUT=5
|
||||||
|
EMAIL_USERNAME=netbox
|
||||||
|
# EMAIL_USE_SSL and EMAIL_USE_TLS are mutually exclusive, i.e. they can't both be `true`!
|
||||||
|
EMAIL_USE_SSL=false
|
||||||
|
EMAIL_USE_TLS=false
|
||||||
|
GRAPHQL_ENABLED=true
|
||||||
|
HOUSEKEEPING_INTERVAL=86400
|
||||||
|
MEDIA_ROOT=/opt/netbox/netbox/media
|
||||||
|
METRICS_ENABLED=false
|
||||||
|
REDIS_CACHE_DATABASE=1
|
||||||
|
REDIS_CACHE_HOST=redis-cache
|
||||||
|
REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY=false
|
||||||
|
REDIS_CACHE_PASSWORD=t4Ph722qJ5QHeQ1qfu36
|
||||||
|
REDIS_CACHE_SSL=false
|
||||||
|
REDIS_DATABASE=0
|
||||||
|
REDIS_HOST=redis
|
||||||
|
REDIS_INSECURE_SKIP_TLS_VERIFY=false
|
||||||
|
REDIS_PASSWORD=H733Kdjndks81
|
||||||
|
REDIS_SSL=false
|
||||||
|
RELEASE_CHECK_URL=https://api.github.com/repos/netbox-community/netbox/releases
|
||||||
|
SECRET_KEY='r(m)9nLGnz$(_q3N4z1k(EFsMCjjjzx08x9VhNVcfd%6RF#r!6DE@+V5Zk2X'
|
||||||
|
WEBHOOKS_ENABLED=true
|
||||||
|
SUPERUSER_API_TOKEN=w5pgWXPqZVmngLN4w4XwuPvZfUC72ytDxnnHgEmI
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
POSTGRES_DB=netbox
|
||||||
|
POSTGRES_PASSWORD=J5brHrAXFLQSif0K
|
||||||
|
POSTGRES_USER=netbox
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
REDIS_PASSWORD=t4Ph722qJ5QHeQ1qfu36
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
REDIS_PASSWORD=H733Kdjndks81
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
apiRoot = "http://localhost:9999/api/plugins/netbox-dns"
|
||||||
|
token = "w5pgWXPqZVmngLN4w4XwuPvZfUC72ytDxnnHgEmI"
|
||||||
|
execdir string
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
_, filename, _, ok := runtime.Caller(0)
|
||||||
|
if !ok {
|
||||||
|
panic("unable to get current filename")
|
||||||
|
}
|
||||||
|
execdir = filepath.Dir(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
func post(client *http.Client, path string, filepath string) (string, []byte) {
|
||||||
|
file, err := os.Open(filepath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
stat, _ := file.Stat()
|
||||||
|
req, err := http.NewRequest("POST", apiRoot+path, file)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Token %s", token))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("Accept", "application/json; indent=4")
|
||||||
|
req.ContentLength = stat.Size()
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
content, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
return resp.Status, content
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
nameservers := filepath.Join(execdir, "nameservers.json")
|
||||||
|
zones := filepath.Join(execdir, "zones.json")
|
||||||
|
records := filepath.Join(execdir, "records.json")
|
||||||
|
client := &http.Client{}
|
||||||
|
|
||||||
|
nsStatus, nsContent := post(client, "/nameservers/", nameservers)
|
||||||
|
log.Printf("nameservers: %s\n%s", nsStatus, nsContent)
|
||||||
|
|
||||||
|
zoneStatus, zoneContent := post(client, "/zones/", zones)
|
||||||
|
log.Printf("zones: %s\n%s", zoneStatus, zoneContent)
|
||||||
|
|
||||||
|
recordStatus, recordContent := post(client, "/records/", records)
|
||||||
|
log.Printf("records: %s\n%s", recordStatus, recordContent)
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dns02.example.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,250 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "A",
|
||||||
|
"name": "dns01",
|
||||||
|
"value": "10.0.0.10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "AAAA",
|
||||||
|
"name": "dns01",
|
||||||
|
"value": "2001:db8:dead:beef::1:10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "A",
|
||||||
|
"name": "dns02",
|
||||||
|
"value": "10.0.0.11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "AAAA",
|
||||||
|
"name": "dns02",
|
||||||
|
"value": "2001:db8:dead:beef::1:11"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "A",
|
||||||
|
"name": "aservice",
|
||||||
|
"value": "10.0.0.12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "AAAA",
|
||||||
|
"name": "aservice",
|
||||||
|
"value": "2001:db8:dead:beef::1:12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "MX",
|
||||||
|
"name": "@",
|
||||||
|
"value": "10 mail.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "A",
|
||||||
|
"name": "mail",
|
||||||
|
"value": "10.0.0.13"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "AAAA",
|
||||||
|
"name": "mail",
|
||||||
|
"value": "2001:db8:dead:beef::1:13"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "TXT",
|
||||||
|
"name": "@",
|
||||||
|
"value": "v=spf1 ip4:10.0.0.13 ip6:2001:db8:dead:beef::1:13 a -all"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "TXT",
|
||||||
|
"name": "@",
|
||||||
|
"value": "v=DMARC1;p=none;sp=quarantine;pct=100;rua=admin@example.com;"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "TXT",
|
||||||
|
"name": "@",
|
||||||
|
"value": "\"some value\"\\r\\n\"another value\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "TXT",
|
||||||
|
"name": "@",
|
||||||
|
"value": "\"newline record\"\\n\"second value\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "TXT",
|
||||||
|
"name": "@",
|
||||||
|
"value": "\"my value\" \"second my value\" \"third my value\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "A",
|
||||||
|
"name": "puppet-server-a",
|
||||||
|
"value": "10.0.0.15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "AAAA",
|
||||||
|
"name": "puppet-server-a",
|
||||||
|
"value": "2001:db8:dead:beef::1:15"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "SRV",
|
||||||
|
"name": "_x-puppet._tcp",
|
||||||
|
"value": "0 5 8140 puppet-server-a.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "A",
|
||||||
|
"name": "puppet-server-b",
|
||||||
|
"value": "10.0.0.16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "AAAA",
|
||||||
|
"name": "puppet-server-b",
|
||||||
|
"value": "2001:db8:dead:beef::1:16"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "SRV",
|
||||||
|
"name": "_x-puppet._tcp",
|
||||||
|
"value": "0 5 8140 puppet-server-b.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "A",
|
||||||
|
"name": "web",
|
||||||
|
"value": "10.0.0.17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "AAAA",
|
||||||
|
"name": "web",
|
||||||
|
"value": "2001:db8:dead:beef::1:17"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "CNAME",
|
||||||
|
"name": "www",
|
||||||
|
"value": "web.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "NS",
|
||||||
|
"name": "sub",
|
||||||
|
"value": "dns01.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "NS",
|
||||||
|
"name": "sub",
|
||||||
|
"value": "dns02.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "NS",
|
||||||
|
"name": "subtwo",
|
||||||
|
"value": "dns01.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "example.com"
|
||||||
|
},
|
||||||
|
"type": "NS",
|
||||||
|
"name": "subtwo",
|
||||||
|
"value": "dns02.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "sub.example.com"
|
||||||
|
},
|
||||||
|
"type": "A",
|
||||||
|
"name": "myservice",
|
||||||
|
"value": "10.0.1.10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "sub.example.com"
|
||||||
|
},
|
||||||
|
"type": "AAAA",
|
||||||
|
"name": "myservice",
|
||||||
|
"value": "2001:db8:dead:beef::2:10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "subtwo.example.com"
|
||||||
|
},
|
||||||
|
"type": "A",
|
||||||
|
"name": "myotherservice",
|
||||||
|
"value": "10.0.2.10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"zone": {
|
||||||
|
"name": "subtwo.example.com"
|
||||||
|
},
|
||||||
|
"type": "AAAA",
|
||||||
|
"name": "myotherservice",
|
||||||
|
"value": "2001:db8:dead:beef::3:10"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "example.com",
|
||||||
|
"nameservers": [
|
||||||
|
{
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dns02.example.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_ttl": 3600,
|
||||||
|
"soa_expire": 2419200,
|
||||||
|
"soa_minimum": 3600,
|
||||||
|
"soa_mname": {
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
"soa_ttl": 86400,
|
||||||
|
"soa_refresh": 43200,
|
||||||
|
"soa_retry": 7200,
|
||||||
|
"soa_rname": "admin.example.com",
|
||||||
|
"soa_serial_auto": false,
|
||||||
|
"soa_serial": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "0.0.10.in-addr.arpa",
|
||||||
|
"nameservers": [
|
||||||
|
{
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dns02.example.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_ttl": 3600,
|
||||||
|
"soa_expire": 2419200,
|
||||||
|
"soa_minimum": 3600,
|
||||||
|
"soa_mname": {
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
"soa_ttl": 86400,
|
||||||
|
"soa_refresh": 43200,
|
||||||
|
"soa_retry": 7200,
|
||||||
|
"soa_rname": "admin.example.com",
|
||||||
|
"soa_serial_auto": false,
|
||||||
|
"soa_serial": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa",
|
||||||
|
"nameservers": [
|
||||||
|
{
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dns02.example.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_ttl": 3600,
|
||||||
|
"soa_expire": 2419200,
|
||||||
|
"soa_minimum": 3600,
|
||||||
|
"soa_mname": {
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
"soa_ttl": 86400,
|
||||||
|
"soa_refresh": 43200,
|
||||||
|
"soa_retry": 7200,
|
||||||
|
"soa_rname": "admin.example.com",
|
||||||
|
"soa_serial_auto": false,
|
||||||
|
"soa_serial": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sub.example.com",
|
||||||
|
"nameservers": [
|
||||||
|
{
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dns02.example.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_ttl": 3600,
|
||||||
|
"soa_expire": 2419200,
|
||||||
|
"soa_minimum": 3600,
|
||||||
|
"soa_mname": {
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
"soa_ttl": 86400,
|
||||||
|
"soa_refresh": 43200,
|
||||||
|
"soa_retry": 7200,
|
||||||
|
"soa_rname": "admin.example.com",
|
||||||
|
"soa_serial_auto": false,
|
||||||
|
"soa_serial": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "1.0.10.in-addr.arpa",
|
||||||
|
"nameservers": [
|
||||||
|
{
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dns02.example.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_ttl": 3600,
|
||||||
|
"soa_expire": 2419200,
|
||||||
|
"soa_minimum": 3600,
|
||||||
|
"soa_mname": {
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
"soa_ttl": 86400,
|
||||||
|
"soa_refresh": 43200,
|
||||||
|
"soa_retry": 7200,
|
||||||
|
"soa_rname": "admin.example.com",
|
||||||
|
"soa_serial_auto": false,
|
||||||
|
"soa_serial": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "2.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa",
|
||||||
|
"nameservers": [
|
||||||
|
{
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dns02.example.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_ttl": 3600,
|
||||||
|
"soa_expire": 2419200,
|
||||||
|
"soa_minimum": 3600,
|
||||||
|
"soa_mname": {
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
"soa_ttl": 86400,
|
||||||
|
"soa_refresh": 43200,
|
||||||
|
"soa_retry": 7200,
|
||||||
|
"soa_rname": "admin.example.com",
|
||||||
|
"soa_serial_auto": false,
|
||||||
|
"soa_serial": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "subtwo.example.com",
|
||||||
|
"nameservers": [
|
||||||
|
{
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dns02.example.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_ttl": 3600,
|
||||||
|
"soa_expire": 2419200,
|
||||||
|
"soa_minimum": 3600,
|
||||||
|
"soa_mname": {
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
"soa_ttl": 86400,
|
||||||
|
"soa_refresh": 43200,
|
||||||
|
"soa_retry": 7200,
|
||||||
|
"soa_rname": "admin.example.com",
|
||||||
|
"soa_serial_auto": false,
|
||||||
|
"soa_serial": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "2.0.10.in-addr.arpa",
|
||||||
|
"nameservers": [
|
||||||
|
{
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dns02.example.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_ttl": 3600,
|
||||||
|
"soa_expire": 2419200,
|
||||||
|
"soa_minimum": 3600,
|
||||||
|
"soa_mname": {
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
"soa_ttl": 86400,
|
||||||
|
"soa_refresh": 43200,
|
||||||
|
"soa_retry": 7200,
|
||||||
|
"soa_rname": "admin.example.com",
|
||||||
|
"soa_serial_auto": false,
|
||||||
|
"soa_serial": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "3.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa",
|
||||||
|
"nameservers": [
|
||||||
|
{
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dns02.example.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default_ttl": 3600,
|
||||||
|
"soa_expire": 2419200,
|
||||||
|
"soa_minimum": 3600,
|
||||||
|
"soa_mname": {
|
||||||
|
"name": "dns01.example.com"
|
||||||
|
},
|
||||||
|
"soa_ttl": 86400,
|
||||||
|
"soa_refresh": 43200,
|
||||||
|
"soa_retry": 7200,
|
||||||
|
"soa_rname": "admin.example.com",
|
||||||
|
"soa_serial_auto": false,
|
||||||
|
"soa_serial": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
FROM docker.io/netboxcommunity/netbox:latest
|
||||||
|
|
||||||
|
COPY ./requirements-plugin.txt /opt/netbox/
|
||||||
|
RUN /opt/netbox/venv/bin/pip install \
|
||||||
|
--no-warn-script-location \
|
||||||
|
-r /opt/netbox/requirements-plugin.txt
|
||||||
|
|
||||||
|
COPY configuration/plugins.py /etc/netbox/config/plugins.py
|
||||||
|
RUN SECRET_KEY="dummydummydummydummydummydummydummydummydummydummy" \
|
||||||
|
/opt/netbox/venv/bin/python \
|
||||||
|
/opt/netbox/netbox/manage.py \
|
||||||
|
collectstatic --no-input
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
netbox-plugin-dns >= 0.22.8
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
docker compose -p coredns-netbox-plugin-dns -f ./.testing/docker-compose.yml up -d && \
|
||||||
|
|
||||||
|
until [[ "`docker inspect -f {{.State.Health.Status}} coredns-netbox-plugin-dns-netbox-1`" == "healthy" ]]; do
|
||||||
|
echo "Waiting for Netbox to come online..."
|
||||||
|
sleep 5;
|
||||||
|
done && \
|
||||||
|
|
||||||
|
go run ./.testing/init/init.go
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICHDCCAcKgAwIBAgIUKqkR+rk8a0RIfpAdkRRr/jgdhhQwCgYIKoZIzj0EAwIw
|
||||||
|
bDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp
|
||||||
|
c2NvMRcwFQYDVQQKEw5OZXRib3ggVGVzdGluZzEfMB0GA1UEAxMWTmV0Ym94IFRl
|
||||||
|
c3RpbmcgUm9vdCBDQTAeFw0yNDA1MDIwMDU1MDBaFw0yOTA1MDEwMDU1MDBaMGwx
|
||||||
|
CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
|
||||||
|
bzEXMBUGA1UEChMOTmV0Ym94IFRlc3RpbmcxHzAdBgNVBAMTFk5ldGJveCBUZXN0
|
||||||
|
aW5nIFJvb3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARInLLhQRy22ueR
|
||||||
|
FMUVzpzhllZkEkuOMQzDR83X9B5oKWWIhbEtjILLYOigf4BstV4O4NoaNbTF9gON
|
||||||
|
PRtkVL0ao0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV
|
||||||
|
HQ4EFgQU7MXIkjUgpd/cvpDUOxIDczDKFR4wCgYIKoZIzj0EAwIDSAAwRQIhAPWc
|
||||||
|
++vwGVv0BNU2bWNLDQO8/T0izNv78rHfNuxKx/+OAiATSCSs7yDId9RW02sADVrL
|
||||||
|
4JeoHAlc/qsMltuTeXV+kA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
-----BEGIN EC PRIVATE KEY-----
|
||||||
|
MHcCAQEEILxY99DUkfyC9uFgkLzJoce2BkEwxI2FiBttKptbOFgBoAoGCCqGSM49
|
||||||
|
AwEHoUQDQgAEM1w4sKz9to1SpdZ5whJK41t5JVAYivmFklD87IAQOKXqt5DKAX9r
|
||||||
|
Z8f/95FVt8qGOYkG4OYP4sCfi8g2pnd6Jg==
|
||||||
|
-----END EC PRIVATE KEY-----
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICTzCCAfSgAwIBAgIUaz+i0MMtm6NZ73aB6OPZ7V4N8WIwCgYIKoZIzj0EAwIw
|
||||||
|
bDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNp
|
||||||
|
c2NvMRcwFQYDVQQKEw5OZXRib3ggVGVzdGluZzEfMB0GA1UEAxMWTmV0Ym94IFRl
|
||||||
|
c3RpbmcgUm9vdCBDQTAeFw0yNDA1MDIwMTA0MDBaFw0yOTA1MDEwMTA0MDBaMGsx
|
||||||
|
CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj
|
||||||
|
bzEXMBUGA1UEChMOTmV0Ym94IFRlc3RpbmcxHjAcBgNVBAMTFU5ldGJveCBUZXN0
|
||||||
|
aW5nIENsaWVudDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDNcOLCs/baNUqXW
|
||||||
|
ecISSuNbeSVQGIr5hZJQ/OyAEDil6reQygF/a2fH//eRVbfKhjmJBuDmD+LAn4vI
|
||||||
|
NqZ3eiajdTBzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDAjAM
|
||||||
|
BgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTo33pMbsD0Qe4bfVoEA850oKgqEzAfBgNV
|
||||||
|
HSMEGDAWgBTsxciSNSCl39y+kNQ7EgNzMMoVHjAKBggqhkjOPQQDAgNJADBGAiEA
|
||||||
|
qLETeHL3iuG1Vxdey+VhEU4q5Xfp59mvR6YJksBT3oECIQCRSDRSo0t9nQh6U9wg
|
||||||
|
C/KvjPFLc0pYblQiiuQOlDtjXg==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
Vendored
+15
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch Test",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "test",
|
||||||
|
"program": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"go.testFlags": ["-v"]
|
||||||
|
}
|
||||||
Vendored
+80
@@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "[Start] Netbox test instance",
|
||||||
|
"type": "docker-compose",
|
||||||
|
"dockerCompose": {
|
||||||
|
"projectName": "coredns-netbox-plugin-dns",
|
||||||
|
"up": {
|
||||||
|
"detached": true,
|
||||||
|
"build": true,
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"${workspaceFolder}/.testing/docker-compose.yml"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "[Stop] Netbox test instance",
|
||||||
|
"type": "docker-compose",
|
||||||
|
"dockerCompose": {
|
||||||
|
"projectName": "coredns-netbox-plugin-dns",
|
||||||
|
"down": {
|
||||||
|
"removeVolumes": true
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"${workspaceFolder}/.testing/docker-compose.yml"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Go Coverage",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "go",
|
||||||
|
"args": [
|
||||||
|
"test",
|
||||||
|
"-short",
|
||||||
|
"-coverprofile=${workspaceFolder}/coverage.out",
|
||||||
|
"./..."
|
||||||
|
],
|
||||||
|
"options": {
|
||||||
|
"env": {
|
||||||
|
"CGO_ENABLED": "0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"problemMatcher": {
|
||||||
|
"pattern": {
|
||||||
|
"regexp": ".*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Go View Coverage",
|
||||||
|
"type": "shell",
|
||||||
|
"linux": {
|
||||||
|
"command": "setsid",
|
||||||
|
"args": [
|
||||||
|
"go",
|
||||||
|
"tool",
|
||||||
|
"cover",
|
||||||
|
"-html=${workspaceFolder}/coverage.out"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"windows": {
|
||||||
|
"command": "go",
|
||||||
|
"args": [
|
||||||
|
"tool",
|
||||||
|
"cover",
|
||||||
|
"-html=${workspaceFolder}/coverage.out"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependsOn": "Go Coverage",
|
||||||
|
"problemMatcher": {
|
||||||
|
"pattern": {
|
||||||
|
"regexp": ".*"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 W Anders <w@doubleu.codes>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
# netboxdns
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/github.com/doubleu-labs/coredns-netbox-plugin-dns)
|
||||||
|
[](https://sonarcloud.io/summary/overall?id=doubleu-labs_coredns-netbox-plugin-dns)
|
||||||
|
[](https://goreportcard.com/report/github.com/doubleu-labs/coredns-netbox-plugin-dns)
|
||||||
|
|
||||||
|
*netboxdns* - provides resolution using
|
||||||
|
[Netbox DNS Plugin (netbox-plugin-dns)](https://github.com/peteeckel/netbox-plugin-dns)
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
The *netboxdns* plugin provides resolution for zones configured using
|
||||||
|
[netbox-plugin-dns](https://github.com/peteeckel/netbox-plugin-dns).
|
||||||
|
|
||||||
|
**Depends on `netbox-plugin-dns` version `0.22.8` or greater.**
|
||||||
|
|
||||||
|
The account that the API token is tied to will need the following permissions:
|
||||||
|
|
||||||
|
- `netbox_dns.view_zone`
|
||||||
|
- `netbox_dns.view_record`
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
Available configuration options:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
netboxdns [ZONES...] {
|
||||||
|
token TOKEN
|
||||||
|
url URL
|
||||||
|
timeout DURATION
|
||||||
|
fallthrough [ZONES...]
|
||||||
|
tls CERT KET CACERT
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* **ZONES**: A space-delimited list of zones that the plugin will answer for
|
||||||
|
|
||||||
|
* **`token TOKEN` (REQUIRED)**: The API token used to authenticate requests
|
||||||
|
to the Netbox instance
|
||||||
|
|
||||||
|
* **`url URL` (REQUIRED)**: The URL that Netbox is accessible at
|
||||||
|
|
||||||
|
* **`timeout DURATION`** (DEFAULT=`5s`): A duration to time-out requests to the
|
||||||
|
Netbox API
|
||||||
|
|
||||||
|
* **`fallthrough`**: If no record exists, send the request to the next plugin.
|
||||||
|
* **(OPTIONAL) `ZONES...`**: A space-delimited list of zones that requests
|
||||||
|
should be forwarded to the next plugin. If requests are not in the specified
|
||||||
|
zones, an empty reponse is returned.
|
||||||
|
|
||||||
|
* **`tls`**: Used to authenticate to the Netbox instance if it is using HTTPS.
|
||||||
|
* `0 arguments`: Creates a TLS configuration that uses system CA certificates
|
||||||
|
to validate the connection to the Netbox instance. Use when Netbox is using
|
||||||
|
a server certificate signed by a public CA. The client is not authenticated
|
||||||
|
by the server.
|
||||||
|
|
||||||
|
* `1 argument`: Path to the CA PEM file. Creates a TLS configuration that uses
|
||||||
|
the specified CA certificate to validate the connection to the Netbox
|
||||||
|
instance. Use when Netbox is using a server certificate signed by a private
|
||||||
|
CA. The client is not authenticated by the server.
|
||||||
|
|
||||||
|
* `2 arguments`: Paths to the client certificate and private key PEM files.
|
||||||
|
Creates a TLS configuration that uses system CA certificates to validate the
|
||||||
|
connection to the Netbox instance. Use when certificates are needed to
|
||||||
|
authenticate to the Netbox instance (mTLS) (Netbox Cloud).
|
||||||
|
|
||||||
|
* `3 arguments`: Paths to the client certificate, private key, and CA PEM
|
||||||
|
files. Creates a TLS configuration that uses the specified CA certificate to
|
||||||
|
validate the connection to the Netbox instance. Use when certificates are
|
||||||
|
needed to authenticate to the Netbox instance (mTLS) and Netbox is using a
|
||||||
|
server certificate signed by a private CA.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Clone the [coredns](https://github.com/coredns/coredns) repository and change
|
||||||
|
into it's directory.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/coredns/coredns.git
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd coredns
|
||||||
|
```
|
||||||
|
|
||||||
|
Fetch the plugin and add it to `coredns`'s `go.mod` file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get -u github.com/doubleu-labs/coredns-netbox-plugin-dns
|
||||||
|
```
|
||||||
|
|
||||||
|
Update `plugin.cfg` in the root of the directory. The `netboxdns` declaration
|
||||||
|
should be inserted after `cache` if you want responses from Netbox to be
|
||||||
|
cached.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Using sed
|
||||||
|
sed -i '/^cache:cache/a netboxdns:github.com/doubleu-labs/coredns-netbox-plugin-dns' plugin.cfg
|
||||||
|
```
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Using Powershell
|
||||||
|
(Get-Content plugin.cfg).`
|
||||||
|
Replace("cache:cache", "cache:cache`nnetboxdns:github.com/doubleu-labs/coredns-netbox-plugin-dns") | `
|
||||||
|
Set-Content -Path plugin.cfg
|
||||||
|
```
|
||||||
|
|
||||||
|
Build using `make`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if `make` is not available, simply run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go generate && go build
|
||||||
|
```
|
||||||
|
|
||||||
|
The `coredns` binary will be in the root of the project directory, unless
|
||||||
|
otherwise specified by the `-o` flag.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
A [Docker Compose file](./.testing/docker-compose.yml) is provided to setup a
|
||||||
|
minimal Netbox instance to run tests against. If using Visual Studio Code, two
|
||||||
|
tasks are configured to start and stop this instance. Use `Ctrl+Shift+P` and
|
||||||
|
select `[Start] Netbox test instance`.
|
||||||
|
|
||||||
|
Check that Netbox is finished with the initial setup by watching the container
|
||||||
|
logs using:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker logs -f coredns-netbox-plugin-dns-netbox-1
|
||||||
|
```
|
||||||
|
|
||||||
|
The test instance will be available at
|
||||||
|
[http://localhost:9999](http://localhost:9999/) with the `admin:admin` username
|
||||||
|
and password. When you see healthcheck requests, invoke
|
||||||
|
[init.go](./.testing/init/init.go) to populate the test dataset.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go run .testing/init/init.go
|
||||||
|
```
|
||||||
|
|
||||||
|
This standalone application POSTs the contents of the
|
||||||
|
JSON files in [.testing/init](./.testing/init/) to populate the database. If
|
||||||
|
adding a new feature or bugfix that requires additional records, be sure to add
|
||||||
|
the Zone or Record to the appropriate JSON file.
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
module github.com/doubleu-labs/coredns-netbox-plugin-dns
|
||||||
|
|
||||||
|
go 1.22.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/coredns/caddy v1.1.1
|
||||||
|
github.com/coredns/coredns v1.11.3
|
||||||
|
github.com/miekg/dns v1.1.59
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/apparentlymart/go-cidr v1.1.0 // indirect
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
|
||||||
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20240430035430-e4905b036c4e // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
|
github.com/onsi/ginkgo/v2 v2.17.2 // indirect
|
||||||
|
github.com/opentracing/opentracing-go v1.2.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
github.com/prometheus/client_golang v1.19.0 // indirect
|
||||||
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
|
github.com/prometheus/common v0.53.0 // indirect
|
||||||
|
github.com/prometheus/procfs v0.14.0 // indirect
|
||||||
|
github.com/quic-go/quic-go v0.43.0 // indirect
|
||||||
|
go.uber.org/mock v0.4.0 // indirect
|
||||||
|
golang.org/x/crypto v0.22.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect
|
||||||
|
golang.org/x/mod v0.17.0 // indirect
|
||||||
|
golang.org/x/net v0.24.0 // indirect
|
||||||
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
|
golang.org/x/sys v0.19.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
golang.org/x/tools v0.20.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 // indirect
|
||||||
|
google.golang.org/grpc v1.63.2 // indirect
|
||||||
|
google.golang.org/protobuf v1.34.0 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
|
||||||
|
github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
|
||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/coredns/caddy v1.1.1 h1:2eYKZT7i6yxIfGP3qLJoJ7HAsDJqYB+X68g4NYjSrE0=
|
||||||
|
github.com/coredns/caddy v1.1.1/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
|
||||||
|
github.com/coredns/coredns v1.11.3 h1:8RjnpZc42db5th84/QJKH2i137ecJdzZK1HJwhetSPk=
|
||||||
|
github.com/coredns/coredns v1.11.3/go.mod h1:lqFkDsHjEUdY7LJ75Nib3lwqJGip6ewWOqNIf8OavIQ=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||||
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
|
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||||
|
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
|
||||||
|
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||||
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/pprof v0.0.0-20240430035430-e4905b036c4e h1:RsXNnXE59RTt8o3DcA+w7ICdRfR2l+Bb5aE0YMpNTO8=
|
||||||
|
github.com/google/pprof v0.0.0-20240430035430-e4905b036c4e/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||||
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
|
||||||
|
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||||
|
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
|
github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
|
||||||
|
github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.17.2 h1:7eMhcy3GimbsA3hEnVKdw/PQM9XN9krpKVXsZdph0/g=
|
||||||
|
github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
|
||||||
|
github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE=
|
||||||
|
github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
|
||||||
|
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
|
||||||
|
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||||
|
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||||
|
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||||
|
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||||
|
github.com/prometheus/common v0.53.0 h1:U2pL9w9nmJwJDa4qqLQ3ZaePJ6ZTwt7cMD3AG3+aLCE=
|
||||||
|
github.com/prometheus/common v0.53.0/go.mod h1:BrxBKv3FWBIGXw89Mg1AeBq7FSyRzXWI3l3e7W3RN5U=
|
||||||
|
github.com/prometheus/procfs v0.14.0 h1:Lw4VdGGoKEZilJsayHf0B+9YgLGREba2C6xr+Fdfq6s=
|
||||||
|
github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ=
|
||||||
|
github.com/quic-go/quic-go v0.43.0 h1:sjtsTKWX0dsHpuMJvLxGqoQdtgJnbAPWY+W+5vjYW/g=
|
||||||
|
github.com/quic-go/quic-go v0.43.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||||
|
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||||
|
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||||
|
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||||
|
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
|
||||||
|
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||||
|
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||||
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||||
|
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||||
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||||
|
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
|
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||||
|
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6 h1:DujSIu+2tC9Ht0aPNA7jgj23Iq8Ewi5sgkQ++wdvonE=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||||
|
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||||
|
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||||
|
google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4=
|
||||||
|
google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package netbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type APIRequestClient struct {
|
||||||
|
Client *http.Client
|
||||||
|
NetboxURL *url.URL
|
||||||
|
Token string
|
||||||
|
UserAgent string
|
||||||
|
}
|
||||||
|
|
||||||
|
type APIResultModel interface {
|
||||||
|
Record | Zone
|
||||||
|
}
|
||||||
|
|
||||||
|
type APIManyResponse[T APIResultModel] struct {
|
||||||
|
Count int `json:"count"`
|
||||||
|
Next string `json:"next"`
|
||||||
|
Previous string `json:"previous"`
|
||||||
|
Results []T `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func doGet(
|
||||||
|
requestClient *APIRequestClient,
|
||||||
|
url string,
|
||||||
|
) (*http.Response, error) {
|
||||||
|
request, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Header.Set(
|
||||||
|
"Authorization",
|
||||||
|
fmt.Sprintf("Token %s", requestClient.Token),
|
||||||
|
)
|
||||||
|
|
||||||
|
request.Header.Set("User-Agent", requestClient.UserAgent)
|
||||||
|
|
||||||
|
return requestClient.Client.Do(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func responseError(response *http.Response) error {
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"request error [%d] %q",
|
||||||
|
response.StatusCode,
|
||||||
|
response.Status,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func get[T APIResultModel](
|
||||||
|
requestClient *APIRequestClient,
|
||||||
|
url string,
|
||||||
|
) (T, error) {
|
||||||
|
var out T
|
||||||
|
response, err := doGet(requestClient, url)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
if err := responseError(response); err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
decoder := json.NewDecoder(response.Body)
|
||||||
|
if err := decoder.Decode(&out); err != nil {
|
||||||
|
return out, fmt.Errorf("could not unmarshal response: %w", err)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMany[T APIResultModel](
|
||||||
|
requestClient *APIRequestClient,
|
||||||
|
url string,
|
||||||
|
) ([]T, error) {
|
||||||
|
nextUrl := url
|
||||||
|
var out []T
|
||||||
|
|
||||||
|
for nextUrl != "" {
|
||||||
|
response, err := doGet(requestClient, nextUrl)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
if err := responseError(response); err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiResponse APIManyResponse[T]
|
||||||
|
decoder := json.NewDecoder(response.Body)
|
||||||
|
if err := decoder.Decode(&apiResponse); err != nil {
|
||||||
|
return out, fmt.Errorf("could not unmarshal response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if out == nil {
|
||||||
|
out = make([]T, 0, apiResponse.Count)
|
||||||
|
}
|
||||||
|
out = append(out, apiResponse.Results...)
|
||||||
|
|
||||||
|
nextUrl = apiResponse.Next
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package netbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Record struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
TTL *uint32 `json:"ttl"`
|
||||||
|
Zone Zone `json:"zone"`
|
||||||
|
FQDN string `json:"fqdn"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecordQuery struct {
|
||||||
|
FQDN string
|
||||||
|
Name string
|
||||||
|
Type []string
|
||||||
|
Zone *Zone
|
||||||
|
}
|
||||||
|
|
||||||
|
func (recordQuery *RecordQuery) Encode() string {
|
||||||
|
out := url.Values{}
|
||||||
|
|
||||||
|
if recordQuery.FQDN != "" {
|
||||||
|
out.Set("fqdn", recordQuery.FQDN)
|
||||||
|
}
|
||||||
|
|
||||||
|
if recordQuery.Name != "" {
|
||||||
|
out.Set("name", recordQuery.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(recordQuery.Type) != 0 {
|
||||||
|
for _, t := range recordQuery.Type {
|
||||||
|
out.Add("type", t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if recordQuery.Zone != nil {
|
||||||
|
out.Set("zone_id", strconv.Itoa(recordQuery.Zone.ID))
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlRecords(netboxurl *url.URL) *url.URL {
|
||||||
|
return netboxurl.JoinPath("records", "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRecordsQuery(
|
||||||
|
requestClient *APIRequestClient,
|
||||||
|
query *RecordQuery,
|
||||||
|
) ([]Record, error) {
|
||||||
|
requestUrl := urlRecords(requestClient.NetboxURL)
|
||||||
|
requestUrl.RawQuery = query.Encode()
|
||||||
|
records, err := getMany[Record](requestClient, requestUrl.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if query.Zone != nil {
|
||||||
|
for k, record := range records {
|
||||||
|
if record.TTL == nil {
|
||||||
|
records[k].TTL = &query.Zone.DefaultTTL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resolvedRecords, err := resolveRecordTTLs(requestClient, records)
|
||||||
|
if err != nil {
|
||||||
|
return records, err
|
||||||
|
}
|
||||||
|
records = resolvedRecords
|
||||||
|
}
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveRecordTTLs(
|
||||||
|
requestClient *APIRequestClient,
|
||||||
|
records []Record,
|
||||||
|
) ([]Record, error) {
|
||||||
|
zoneTTL := make(map[int]uint32)
|
||||||
|
for k, record := range records {
|
||||||
|
if record.TTL != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ttl, ok := zoneTTL[record.Zone.ID]; ok {
|
||||||
|
records[k].TTL = &ttl
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
zoneUrl := urlZoneID(requestClient.NetboxURL, record.Zone.ID)
|
||||||
|
zone, err := get[Zone](requestClient, zoneUrl.String())
|
||||||
|
if err != nil {
|
||||||
|
return records, err
|
||||||
|
}
|
||||||
|
zoneTTL[zone.ID] = zone.DefaultTTL
|
||||||
|
records[k].TTL = &zone.DefaultTTL
|
||||||
|
}
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package netbox
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Zone struct {
|
||||||
|
DefaultTTL uint32 `json:"default_ttl"`
|
||||||
|
ID int `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
NameServers []SOAMName `json:"nameservers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SOAMName struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlZones(netboxurl *url.URL) *url.URL {
|
||||||
|
return netboxurl.JoinPath("zones", "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlZoneID(netboxurl *url.URL, id int) *url.URL {
|
||||||
|
return netboxurl.JoinPath("zones", "/", strconv.Itoa(id), "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetZones(requestClient *APIRequestClient) ([]Zone, error) {
|
||||||
|
requestUrl := urlZones(requestClient.NetboxURL)
|
||||||
|
zones, err := getMany[Zone](requestClient, requestUrl.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return zones, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
package netboxdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/doubleu-labs/coredns-netbox-plugin-dns/internal/netbox"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type lookupResult int
|
||||||
|
|
||||||
|
const (
|
||||||
|
lookupSuccess lookupResult = iota
|
||||||
|
lookupNameError // NXDomain
|
||||||
|
lookupDelegation // Delegate, non-authoritative
|
||||||
|
)
|
||||||
|
|
||||||
|
type lookupResponse struct {
|
||||||
|
Answer []dns.RR
|
||||||
|
Ns []dns.RR
|
||||||
|
Extra []dns.RR
|
||||||
|
LookupResult lookupResult
|
||||||
|
}
|
||||||
|
|
||||||
|
func (netboxdns *NetboxDNS) lookup(
|
||||||
|
name string,
|
||||||
|
qtype uint16,
|
||||||
|
family int,
|
||||||
|
) (*lookupResponse, error) {
|
||||||
|
nameTrimmed := strings.TrimSuffix(name, ".")
|
||||||
|
// check if zone exists on Netbox
|
||||||
|
zone, err := netboxdns.matchZone(nameTrimmed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if zone == nil {
|
||||||
|
return &lookupResponse{LookupResult: lookupNameError}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if qname is for zone origin
|
||||||
|
if nameTrimmed == zone.Name {
|
||||||
|
originResponse, err := netboxdns.processOrigin(qtype, zone, family)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if originResponse != nil {
|
||||||
|
return originResponse, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookup exact request
|
||||||
|
direct, err := netboxdns.lookupDirect(nameTrimmed, qtype, zone, family)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if direct != nil {
|
||||||
|
return direct, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no exact records exist for the request, check if the qname is a
|
||||||
|
// delegate zone
|
||||||
|
delegate, err := netboxdns.lookupDelegate(nameTrimmed, zone, family)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if delegate != nil {
|
||||||
|
return delegate, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &lookupResponse{LookupResult: lookupNameError}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (netboxdns *NetboxDNS) matchZone(qname string) (*netbox.Zone, error) {
|
||||||
|
managedZones, err := netbox.GetZones(netboxdns.requestClient)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var out *netbox.Zone
|
||||||
|
for _, managedZone := range managedZones {
|
||||||
|
if dns.IsSubDomain(managedZone.Name, qname) {
|
||||||
|
if out == nil {
|
||||||
|
out = &managedZone
|
||||||
|
}
|
||||||
|
if len(managedZone.Name) > len(out.Name) {
|
||||||
|
out = &managedZone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (netboxdns *NetboxDNS) processOrigin(
|
||||||
|
qtype uint16,
|
||||||
|
zone *netbox.Zone,
|
||||||
|
family int,
|
||||||
|
) (*lookupResponse, error) {
|
||||||
|
var queryType []string
|
||||||
|
switch qtype {
|
||||||
|
case dns.TypeSOA:
|
||||||
|
queryType = []string{"SOA", "NS"}
|
||||||
|
case dns.TypeNS:
|
||||||
|
queryType = []string{"NS"}
|
||||||
|
default:
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
records, err := netbox.GetRecordsQuery(
|
||||||
|
netboxdns.requestClient,
|
||||||
|
&netbox.RecordQuery{
|
||||||
|
Name: "@",
|
||||||
|
Type: queryType,
|
||||||
|
Zone: zone,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rrs, err := recordsToRR(records)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
answer := filterRRByType(rrs, dns.TypeSOA)
|
||||||
|
ns := filterRRByType(rrs, dns.TypeNS)
|
||||||
|
extraRecords, err := netboxdns.processExtra(ns, zone, family)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(extraRecords) == 0 {
|
||||||
|
// if no A/AAAA records exist for the NS in the specified zone, check if
|
||||||
|
// the server has records anywhere
|
||||||
|
extraRecords, err = netboxdns.processExtra(ns, nil, family)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
extra, err := recordsToRR(extraRecords)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if qtype == dns.TypeNS {
|
||||||
|
answer = ns
|
||||||
|
ns = nil
|
||||||
|
}
|
||||||
|
return &lookupResponse{
|
||||||
|
Answer: answer,
|
||||||
|
Ns: ns,
|
||||||
|
Extra: extra,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (netboxdns *NetboxDNS) processExtra(
|
||||||
|
answer []dns.RR,
|
||||||
|
zone *netbox.Zone,
|
||||||
|
family int,
|
||||||
|
) ([]netbox.Record, error) {
|
||||||
|
var out []netbox.Record
|
||||||
|
for _, rr := range answer {
|
||||||
|
name := ""
|
||||||
|
switch t := rr.(type) {
|
||||||
|
case *dns.SRV:
|
||||||
|
name = t.Target
|
||||||
|
case *dns.MX:
|
||||||
|
name = t.Mx
|
||||||
|
case *dns.NS:
|
||||||
|
name = t.Ns
|
||||||
|
case *dns.CNAME:
|
||||||
|
name = t.Target
|
||||||
|
}
|
||||||
|
if len(name) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var reqType []string
|
||||||
|
switch family {
|
||||||
|
case 1:
|
||||||
|
reqType = []string{"A"}
|
||||||
|
case 2:
|
||||||
|
reqType = []string{"AAAA"}
|
||||||
|
}
|
||||||
|
records, err := netbox.GetRecordsQuery(
|
||||||
|
netboxdns.requestClient,
|
||||||
|
&netbox.RecordQuery{
|
||||||
|
FQDN: strings.TrimSuffix(name, "."),
|
||||||
|
Type: reqType,
|
||||||
|
Zone: zone,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
out = append(out, records...)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (netboxdns *NetboxDNS) lookupDirect(
|
||||||
|
qname string,
|
||||||
|
qtype uint16,
|
||||||
|
zone *netbox.Zone,
|
||||||
|
family int,
|
||||||
|
) (*lookupResponse, error) {
|
||||||
|
records, err := netbox.GetRecordsQuery(
|
||||||
|
netboxdns.requestClient,
|
||||||
|
&netbox.RecordQuery{
|
||||||
|
FQDN: qname,
|
||||||
|
Type: []string{dns.TypeToString[qtype]},
|
||||||
|
Zone: zone,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(records) > 0 {
|
||||||
|
answer, err := recordsToRR(records)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
extraRecords, err := netboxdns.processExtra(answer, zone, family)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
extra, err := recordsToRR(extraRecords)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if qtype == dns.TypeCNAME {
|
||||||
|
answer = append(answer, extra...)
|
||||||
|
extra = nil
|
||||||
|
}
|
||||||
|
return &lookupResponse{
|
||||||
|
Answer: answer,
|
||||||
|
Extra: extra,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (netboxdns *NetboxDNS) lookupDelegate(
|
||||||
|
qname string,
|
||||||
|
zone *netbox.Zone,
|
||||||
|
family int,
|
||||||
|
) (*lookupResponse, error) {
|
||||||
|
records, err := netbox.GetRecordsQuery(
|
||||||
|
netboxdns.requestClient,
|
||||||
|
&netbox.RecordQuery{
|
||||||
|
FQDN: qname,
|
||||||
|
Type: []string{"NS"},
|
||||||
|
Zone: zone,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(records) > 0 {
|
||||||
|
ns, err := recordsToRR(records)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
extraRecords, err := netboxdns.processExtra(ns, nil, family)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
extra, err := recordsToRR(extraRecords)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &lookupResponse{
|
||||||
|
Ns: ns,
|
||||||
|
Extra: extra,
|
||||||
|
LookupResult: lookupDelegation,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
package netboxdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coredns/coredns/plugin"
|
||||||
|
"github.com/coredns/coredns/plugin/pkg/fall"
|
||||||
|
"github.com/coredns/coredns/plugin/pkg/log"
|
||||||
|
"github.com/coredns/coredns/request"
|
||||||
|
"github.com/doubleu-labs/coredns-netbox-plugin-dns/internal/netbox"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultHTTPClientTimeout time.Duration = time.Second * 5
|
||||||
|
pluginName string = "netboxdns"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger log.P
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logger = log.NewWithPlugin(pluginName)
|
||||||
|
}
|
||||||
|
|
||||||
|
type NetboxDNS struct {
|
||||||
|
Next plugin.Handler
|
||||||
|
|
||||||
|
requestClient *netbox.APIRequestClient
|
||||||
|
|
||||||
|
zones []string
|
||||||
|
fall fall.F
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNetboxDNS() *NetboxDNS {
|
||||||
|
return &NetboxDNS{
|
||||||
|
requestClient: &netbox.APIRequestClient{
|
||||||
|
Client: &http.Client{
|
||||||
|
Timeout: defaultHTTPClientTimeout,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
zones: []string{"."},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name implements the plugin.Handler interface
|
||||||
|
func (NetboxDNS) Name() string {
|
||||||
|
return pluginName
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeDNS implements the plugin.Handler interface
|
||||||
|
func (netboxdns *NetboxDNS) ServeDNS(
|
||||||
|
reqContext context.Context,
|
||||||
|
respWriter dns.ResponseWriter,
|
||||||
|
reqMsg *dns.Msg,
|
||||||
|
) (int, error) {
|
||||||
|
state := request.Request{W: respWriter, Req: reqMsg}
|
||||||
|
qname := state.QName()
|
||||||
|
qtype := state.QType()
|
||||||
|
|
||||||
|
// check if plugin is configured to respond to the requested zone
|
||||||
|
respondingZone := plugin.Zones(netboxdns.zones).Matches(qname)
|
||||||
|
if respondingZone == "" {
|
||||||
|
return netboxdns.nextOrFailure(reqContext, respWriter, reqMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := netboxdns.lookup(qname, qtype, state.Family())
|
||||||
|
if err != nil {
|
||||||
|
return dns.RcodeServerFailure, err
|
||||||
|
}
|
||||||
|
if response.LookupResult == lookupNameError &&
|
||||||
|
netboxdns.fall.Through(qname) {
|
||||||
|
return netboxdns.nextOrFailure(reqContext, respWriter, reqMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
respMsg := &dns.Msg{
|
||||||
|
Answer: response.Answer,
|
||||||
|
Ns: response.Ns,
|
||||||
|
Extra: response.Extra,
|
||||||
|
}
|
||||||
|
respMsg.SetReply(reqMsg)
|
||||||
|
respMsg.Authoritative = true
|
||||||
|
|
||||||
|
switch response.LookupResult {
|
||||||
|
case lookupSuccess:
|
||||||
|
case lookupNameError:
|
||||||
|
respMsg.Rcode = dns.RcodeNameError
|
||||||
|
case lookupDelegation:
|
||||||
|
respMsg.Authoritative = false
|
||||||
|
}
|
||||||
|
|
||||||
|
respWriter.WriteMsg(respMsg)
|
||||||
|
return dns.RcodeSuccess, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (netboxdns *NetboxDNS) nextOrFailure(
|
||||||
|
ctx context.Context,
|
||||||
|
writer dns.ResponseWriter,
|
||||||
|
request *dns.Msg,
|
||||||
|
) (int, error) {
|
||||||
|
return plugin.NextOrFailure(
|
||||||
|
pluginName,
|
||||||
|
netboxdns.Next,
|
||||||
|
ctx,
|
||||||
|
writer,
|
||||||
|
request,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,788 @@
|
|||||||
|
package netboxdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
||||||
|
"github.com/coredns/coredns/plugin/test"
|
||||||
|
"github.com/doubleu-labs/coredns-netbox-plugin-dns/internal/netbox"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPluginName(t *testing.T) {
|
||||||
|
netboxdns := &NetboxDNS{}
|
||||||
|
name := netboxdns.Name()
|
||||||
|
if name != pluginName {
|
||||||
|
t.Errorf("plugin name is wrong. how did this happen...")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testFamily int
|
||||||
|
|
||||||
|
const (
|
||||||
|
testFamilyV4 testFamily = iota
|
||||||
|
testFamilyV6
|
||||||
|
)
|
||||||
|
|
||||||
|
var testFamilyToString map[testFamily]string = map[testFamily]string{
|
||||||
|
testFamilyV4: "V4",
|
||||||
|
testFamilyV6: "V6",
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
testInstanceToken string = "w5pgWXPqZVmngLN4w4XwuPvZfUC72ytDxnnHgEmI"
|
||||||
|
testInstanceUrlHost string = "localhost:9999"
|
||||||
|
testInstanceUrlPath string = "/api/plugins/netbox-dns/"
|
||||||
|
)
|
||||||
|
|
||||||
|
var netboxdnsPlugin NetboxDNS = NetboxDNS{
|
||||||
|
Next: test.ErrorHandler(),
|
||||||
|
zones: []string{"."},
|
||||||
|
requestClient: &netbox.APIRequestClient{
|
||||||
|
Client: &http.Client{
|
||||||
|
Timeout: defaultHTTPClientTimeout,
|
||||||
|
},
|
||||||
|
NetboxURL: &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: testInstanceUrlHost,
|
||||||
|
Path: testInstanceUrlPath,
|
||||||
|
},
|
||||||
|
Token: testInstanceToken,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunTestLookup(t *testing.T, tcs []test.Case, family testFamily) {
|
||||||
|
for _, tc := range tcs {
|
||||||
|
tcName := fmt.Sprintf(
|
||||||
|
"%s %s %s",
|
||||||
|
tc.Qname,
|
||||||
|
dns.TypeToString[tc.Qtype],
|
||||||
|
testFamilyToString[family],
|
||||||
|
)
|
||||||
|
t.Run(tcName, func(t *testing.T) {
|
||||||
|
msg := tc.Msg()
|
||||||
|
respWriter := GetTestResponseWriter(family)
|
||||||
|
rec := dnstest.NewRecorder(respWriter)
|
||||||
|
_, err := netboxdnsPlugin.ServeDNS(context.Background(), rec, msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp := rec.Msg
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatal("got nil response message")
|
||||||
|
}
|
||||||
|
if ok := RunTestLookupCheckCNAME(t, tc, resp); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := test.SortAndCheck(resp, tc); err != nil {
|
||||||
|
t.Logf("%s\n", rec.Msg)
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTestResponseWriter(family testFamily) dns.ResponseWriter {
|
||||||
|
var respWriter dns.ResponseWriter
|
||||||
|
switch family {
|
||||||
|
case testFamilyV4:
|
||||||
|
respWriter = &test.ResponseWriter{}
|
||||||
|
case testFamilyV6:
|
||||||
|
respWriter = &test.ResponseWriter6{}
|
||||||
|
}
|
||||||
|
return respWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunTestLookupCheckCNAME(t *testing.T, tc test.Case, resp *dns.Msg) bool {
|
||||||
|
if err := test.CNAMEOrder(resp); err != nil {
|
||||||
|
t.Errorf("cname response out of order")
|
||||||
|
}
|
||||||
|
if tc.Qtype == dns.TypeCNAME {
|
||||||
|
if err := test.Header(tc, resp); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := test.Section(tc, test.Answer, resp.Answer); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := test.Section(tc, test.Ns, resp.Ns); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if err := test.Section(tc, test.Extra, resp.Extra); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
exampledotcomName string = "example.com."
|
||||||
|
subdotexampledotcomName string = "sub.example.com."
|
||||||
|
subtwodotexampledotcomName string = "subtwo.example.com."
|
||||||
|
|
||||||
|
exampledotcomNS []dns.RR = []dns.RR{
|
||||||
|
test.NS("example.com. 3600 IN NS dns01.example.com."),
|
||||||
|
test.NS("example.com. 3600 IN NS dns02.example.com."),
|
||||||
|
}
|
||||||
|
subdotexampledotcomNS []dns.RR = []dns.RR{
|
||||||
|
test.NS("sub.example.com. 3600 IN NS dns01.example.com."),
|
||||||
|
test.NS("sub.example.com. 3600 IN NS dns02.example.com."),
|
||||||
|
}
|
||||||
|
subtwodotexampledotcomNS []dns.RR = []dns.RR{
|
||||||
|
test.NS("subtwo.example.com. 3600 IN NS dns01.example.com."),
|
||||||
|
test.NS("subtwo.example.com. 3600 IN NS dns02.example.com."),
|
||||||
|
}
|
||||||
|
exampledotcomNS1Record4 dns.RR = test.A("dns01.example.com. 3600 IN A 10.0.0.10")
|
||||||
|
exampledotcomNS2Record4 dns.RR = test.A("dns02.example.com. 3600 IN A 10.0.0.11")
|
||||||
|
exampledotcomNSAddr4 []dns.RR = []dns.RR{
|
||||||
|
exampledotcomNS1Record4,
|
||||||
|
exampledotcomNS2Record4,
|
||||||
|
}
|
||||||
|
exampledotcomNS1Record6 dns.RR = test.AAAA("dns01.example.com. 3600 IN AAAA 2001:db8:dead:beef::1:10")
|
||||||
|
exampledotcomNS2Record6 dns.RR = test.AAAA("dns02.example.com. 3600 IN AAAA 2001:db8:dead:beef::1:11")
|
||||||
|
exampledotcomNSAddr6 []dns.RR = []dns.RR{
|
||||||
|
exampledotcomNS1Record6,
|
||||||
|
exampledotcomNS2Record6,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
testLookupForwardZonesCasesv4 []test.Case = []test.Case{
|
||||||
|
{
|
||||||
|
Qname: exampledotcomName, Qtype: dns.TypeSOA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.SOA("example.com. 86400 IN SOA dns01.example.com. admin.example.com. 1 43200 7200 2419200 3600"),
|
||||||
|
},
|
||||||
|
Ns: exampledotcomNS,
|
||||||
|
Extra: exampledotcomNSAddr4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: subdotexampledotcomName, Qtype: dns.TypeSOA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.SOA("sub.example.com. 86400 IN SOA dns01.example.com. admin.example.com. 1 43200 7200 2419200 3600"),
|
||||||
|
},
|
||||||
|
Ns: []dns.RR{
|
||||||
|
test.NS("sub.example.com. 3600 IN NS dns01.example.com"),
|
||||||
|
test.NS("sub.example.com. 3600 IN NS dns02.example.com"),
|
||||||
|
},
|
||||||
|
Extra: exampledotcomNSAddr4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: subtwodotexampledotcomName, Qtype: dns.TypeSOA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.SOA("subtwo.example.com. 86400 IN SOA dns01.example.com. admin.example.com. 1 43200 7200 2419200 3600"),
|
||||||
|
},
|
||||||
|
Ns: []dns.RR{
|
||||||
|
test.NS("subtwo.example.com. 3600 IN NS dns01.example.com"),
|
||||||
|
test.NS("subtwo.example.com. 3600 IN NS dns02.example.com"),
|
||||||
|
},
|
||||||
|
Extra: exampledotcomNSAddr4,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testLookupReverseZonesCasesv4 []test.Case = []test.Case{
|
||||||
|
{
|
||||||
|
Qname: "0.0.10.in-addr.arpa.", Qtype: dns.TypeSOA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.SOA("0.0.10.in-addr.arpa. 86400 IN SOA dns01.example.com. admin.example.com. 1 43200 7200 2419200 3600"),
|
||||||
|
},
|
||||||
|
Ns: []dns.RR{
|
||||||
|
test.NS("0.0.10.in-addr.arpa. 3600 IN NS dns01.example.com"),
|
||||||
|
test.NS("0.0.10.in-addr.arpa. 3600 IN NS dns02.example.com"),
|
||||||
|
},
|
||||||
|
Extra: exampledotcomNSAddr4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "1.0.10.in-addr.arpa.", Qtype: dns.TypeSOA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.SOA("1.0.10.in-addr.arpa. 86400 IN SOA dns01.example.com. admin.example.com. 1 43200 7200 2419200 3600"),
|
||||||
|
},
|
||||||
|
Ns: []dns.RR{
|
||||||
|
test.NS("1.0.10.in-addr.arpa. 3600 IN NS dns01.example.com"),
|
||||||
|
test.NS("1.0.10.in-addr.arpa. 3600 IN NS dns02.example.com"),
|
||||||
|
},
|
||||||
|
Extra: exampledotcomNSAddr4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "2.0.10.in-addr.arpa.", Qtype: dns.TypeSOA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.SOA("2.0.10.in-addr.arpa. 86400 IN SOA dns01.example.com. admin.example.com. 1 43200 7200 2419200 3600"),
|
||||||
|
},
|
||||||
|
Ns: []dns.RR{
|
||||||
|
test.NS("2.0.10.in-addr.arpa. 3600 IN NS dns01.example.com"),
|
||||||
|
test.NS("2.0.10.in-addr.arpa. 3600 IN NS dns02.example.com"),
|
||||||
|
},
|
||||||
|
Extra: exampledotcomNSAddr4,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testLookupForwardZonesCasesv6 []test.Case = []test.Case{
|
||||||
|
{
|
||||||
|
Qname: exampledotcomName, Qtype: dns.TypeSOA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.SOA("example.com. 86400 IN SOA dns01.example.com. admin.example.com. 1 43200 7200 2419200 3600"),
|
||||||
|
},
|
||||||
|
Ns: exampledotcomNS,
|
||||||
|
Extra: exampledotcomNSAddr6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: subdotexampledotcomName, Qtype: dns.TypeSOA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.SOA("sub.example.com. 86400 IN SOA dns01.example.com. admin.example.com. 1 43200 7200 2419200 3600"),
|
||||||
|
},
|
||||||
|
Ns: []dns.RR{
|
||||||
|
test.NS("sub.example.com. 3600 IN NS dns01.example.com"),
|
||||||
|
test.NS("sub.example.com. 3600 IN NS dns02.example.com"),
|
||||||
|
},
|
||||||
|
Extra: exampledotcomNSAddr6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: subtwodotexampledotcomName, Qtype: dns.TypeSOA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.SOA("subtwo.example.com. 86400 IN SOA dns01.example.com. admin.example.com. 1 43200 7200 2419200 3600"),
|
||||||
|
},
|
||||||
|
Ns: []dns.RR{
|
||||||
|
test.NS("subtwo.example.com. 3600 IN NS dns01.example.com"),
|
||||||
|
test.NS("subtwo.example.com. 3600 IN NS dns02.example.com"),
|
||||||
|
},
|
||||||
|
Extra: exampledotcomNSAddr6,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testLookupReverseZonesCasesv6 []test.Case = []test.Case{
|
||||||
|
{
|
||||||
|
Qname: "1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa.", Qtype: dns.TypeSOA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.SOA("1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 86400 IN SOA dns01.example.com. admin.example.com. 1 43200 7200 2419200 3600"),
|
||||||
|
},
|
||||||
|
Ns: []dns.RR{
|
||||||
|
test.NS("1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 3600 IN NS dns01.example.com"),
|
||||||
|
test.NS("1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 3600 IN NS dns02.example.com"),
|
||||||
|
},
|
||||||
|
Extra: exampledotcomNSAddr6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "2.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa.", Qtype: dns.TypeSOA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.SOA("2.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 86400 IN SOA dns01.example.com. admin.example.com. 1 43200 7200 2419200 3600"),
|
||||||
|
},
|
||||||
|
Ns: []dns.RR{
|
||||||
|
test.NS("2.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 3600 IN NS dns01.example.com"),
|
||||||
|
test.NS("2.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 3600 IN NS dns02.example.com"),
|
||||||
|
},
|
||||||
|
Extra: exampledotcomNSAddr6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "3.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa.", Qtype: dns.TypeSOA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.SOA("3.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 86400 IN SOA dns01.example.com. admin.example.com. 1 43200 7200 2419200 3600"),
|
||||||
|
},
|
||||||
|
Ns: []dns.RR{
|
||||||
|
test.NS("3.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 3600 IN NS dns01.example.com"),
|
||||||
|
test.NS("3.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 3600 IN NS dns02.example.com"),
|
||||||
|
},
|
||||||
|
Extra: exampledotcomNSAddr6,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLookupZones(t *testing.T) {
|
||||||
|
RunTestLookup(t, testLookupForwardZonesCasesv4, testFamilyV4)
|
||||||
|
RunTestLookup(t, testLookupReverseZonesCasesv4, testFamilyV4)
|
||||||
|
RunTestLookup(t, testLookupForwardZonesCasesv6, testFamilyV6)
|
||||||
|
RunTestLookup(t, testLookupReverseZonesCasesv6, testFamilyV6)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
testLookupRecordV4 []test.Case = []test.Case{
|
||||||
|
{
|
||||||
|
Qname: exampledotcomName, Qtype: dns.TypeNS,
|
||||||
|
Answer: exampledotcomNS,
|
||||||
|
Extra: exampledotcomNSAddr4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "dns01.example.com", Qtype: dns.TypeA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
exampledotcomNS1Record4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "dns02.example.com", Qtype: dns.TypeA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.A("dns02.example.com. 3600 IN A 10.0.0.11"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: exampledotcomName, Qtype: dns.TypeA,
|
||||||
|
Ns: exampledotcomNS,
|
||||||
|
Extra: exampledotcomNSAddr4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "aservice.example.com.", Qtype: dns.TypeA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.A("aservice.example.com. 3600 IN A 10.0.0.12"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: exampledotcomName, Qtype: dns.TypeMX,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.MX("example.com. 3600 IN MX 10 mail.example.com."),
|
||||||
|
},
|
||||||
|
Extra: []dns.RR{
|
||||||
|
test.A("mail.example.com. 3600 IN A 10.0.0.13"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "mail.example.com.", Qtype: dns.TypeA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.A("mail.example.com. 3600 IN A 10.0.0.13"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: exampledotcomName, Qtype: dns.TypeTXT,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.TXT(`example.com. 3600 IN TXT "my value" "second my value" "third my value"`),
|
||||||
|
test.TXT(`example.com. 3600 IN TXT "newline record" "second value"`),
|
||||||
|
test.TXT(`example.com. 3600 IN TXT "some value" "another value"`),
|
||||||
|
test.TXT(`example.com. 3600 IN TXT "v=DMARC1;p=none;sp=quarantine;pct=100;rua=admin@example.com;"`),
|
||||||
|
test.TXT(`example.com. 3600 IN TXT "v=spf1 ip4:10.0.0.13 ip6:2001:db8:dead:beef::1:13 a -all"`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "puppet-server-a.example.com.", Qtype: dns.TypeA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.A("puppet-server-a.example.com. 3600 IN A 10.0.0.15"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "puppet-server-b.example.com.", Qtype: dns.TypeA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.A("puppet-server-b.example.com. 3600 IN A 10.0.0.16"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "_x-puppet._tcp.example.com.", Qtype: dns.TypeSRV,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.SRV("_x-puppet._tcp.example.com. 3600 IN SRV 0 5 8140 puppet-server-a.example.com."),
|
||||||
|
test.SRV("_x-puppet._tcp.example.com. 3600 IN SRV 0 5 8140 puppet-server-b.example.com."),
|
||||||
|
},
|
||||||
|
Extra: []dns.RR{
|
||||||
|
test.A("puppet-server-a.example.com. 3600 IN A 10.0.0.15"),
|
||||||
|
test.A("puppet-server-b.example.com. 3600 IN A 10.0.0.16"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "web.example.com.", Qtype: dns.TypeA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.A("web.example.com. 3600 IN A 10.0.0.17"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "www.example.com.", Qtype: dns.TypeCNAME,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.CNAME("www.example.com. 3600 IN CNAME web.example.com."),
|
||||||
|
test.A("web.example.com. 3600 IN A 10.0.0.17"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: subdotexampledotcomName, Qtype: dns.TypeNS,
|
||||||
|
Answer: subdotexampledotcomNS,
|
||||||
|
Extra: exampledotcomNSAddr4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "myservice.sub.example.com.", Qtype: dns.TypeA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.A("myservice.sub.example.com. 3600 IN A 10.0.1.10"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: subtwodotexampledotcomName, Qtype: dns.TypeNS,
|
||||||
|
Answer: subtwodotexampledotcomNS,
|
||||||
|
Extra: exampledotcomNSAddr4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "myotherservice.subtwo.example.com.", Qtype: dns.TypeA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.A("myotherservice.subtwo.example.com. 3600 IN A 10.0.2.10"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testLookupPTRV4 []test.Case = []test.Case{
|
||||||
|
{
|
||||||
|
Qname: "10.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("10.0.0.10.in-addr.arpa. 3600 IN PTR dns01.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "11.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("11.0.0.10.in-addr.arpa. 3600 IN PTR dns02.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "12.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("12.0.0.10.in-addr.arpa. 3600 IN PTR aservice.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "13.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("13.0.0.10.in-addr.arpa. 3600 IN PTR mail.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "15.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("15.0.0.10.in-addr.arpa. 3600 IN PTR puppet-server-a.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "16.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("16.0.0.10.in-addr.arpa. 3600 IN PTR puppet-server-b.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "17.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("17.0.0.10.in-addr.arpa. 3600 IN PTR web.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "10.1.0.10.in-addr.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("10.1.0.10.in-addr.arpa. 3600 IN PTR myservice.sub.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "10.2.0.10.in-addr.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("10.2.0.10.in-addr.arpa. 3600 IN PTR myotherservice.subtwo.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testLookupRecordV6 []test.Case = []test.Case{
|
||||||
|
{
|
||||||
|
Qname: exampledotcomName, Qtype: dns.TypeNS,
|
||||||
|
Answer: exampledotcomNS,
|
||||||
|
Extra: exampledotcomNSAddr6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "dns01.example.com", Qtype: dns.TypeAAAA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
exampledotcomNS1Record6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "dns02.example.com", Qtype: dns.TypeAAAA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
exampledotcomNS2Record6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: exampledotcomName, Qtype: dns.TypeAAAA,
|
||||||
|
Ns: exampledotcomNS,
|
||||||
|
Extra: exampledotcomNSAddr6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "aservice.example.com.", Qtype: dns.TypeAAAA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.AAAA("aservice.example.com. 3600 IN AAAA 2001:db8:dead:beef::1:12"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: exampledotcomName, Qtype: dns.TypeMX,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.MX("example.com. 3600 IN MX 10 mail.example.com."),
|
||||||
|
},
|
||||||
|
Extra: []dns.RR{
|
||||||
|
test.AAAA("mail.example.com. 3600 IN AAAA 2001:db8:dead:beef::1:13"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "mail.example.com.", Qtype: dns.TypeAAAA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.AAAA("mail.example.com. 3600 IN AAAA 2001:db8:dead:beef::1:13"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: exampledotcomName, Qtype: dns.TypeTXT,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.TXT(`example.com. 3600 IN TXT "my value" "second my value" "third my value"`),
|
||||||
|
test.TXT(`example.com. 3600 IN TXT "newline record" "second value"`),
|
||||||
|
test.TXT(`example.com. 3600 IN TXT "some value" "another value"`),
|
||||||
|
test.TXT(`example.com. 3600 IN TXT "v=DMARC1;p=none;sp=quarantine;pct=100;rua=admin@example.com;"`),
|
||||||
|
test.TXT(`example.com. 3600 IN TXT "v=spf1 ip4:10.0.0.13 ip6:2001:db8:dead:beef::1:13 a -all"`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "puppet-server-a.example.com.", Qtype: dns.TypeAAAA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.AAAA("puppet-server-a.example.com. 3600 IN AAAA 2001:db8:dead:beef::1:15"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "puppet-server-b.example.com.", Qtype: dns.TypeAAAA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.AAAA("puppet-server-b.example.com. 3600 IN AAAA 2001:db8:dead:beef::1:16"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "_x-puppet._tcp.example.com.", Qtype: dns.TypeSRV,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.SRV("_x-puppet._tcp.example.com. 3600 IN SRV 0 5 8140 puppet-server-a.example.com."),
|
||||||
|
test.SRV("_x-puppet._tcp.example.com. 3600 IN SRV 0 5 8140 puppet-server-b.example.com."),
|
||||||
|
},
|
||||||
|
Extra: []dns.RR{
|
||||||
|
test.AAAA("puppet-server-a.example.com. 3600 IN AAAA 2001:db8:dead:beef::1:15"),
|
||||||
|
test.AAAA("puppet-server-b.example.com. 3600 IN AAAA 2001:db8:dead:beef::1:16"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "web.example.com.", Qtype: dns.TypeAAAA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.AAAA("web.example.com. 3600 IN AAAA 2001:db8:dead:beef::1:17"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "www.example.com.", Qtype: dns.TypeCNAME,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.CNAME("www.example.com. 3600 IN CNAME web.example.com."),
|
||||||
|
test.AAAA("web.example.com. 3600 IN AAAA 2001:db8:dead:beef::1:17"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: subdotexampledotcomName, Qtype: dns.TypeNS,
|
||||||
|
Answer: subdotexampledotcomNS,
|
||||||
|
Extra: exampledotcomNSAddr6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "myservice.sub.example.com.", Qtype: dns.TypeAAAA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.AAAA("myservice.sub.example.com. 3600 IN AAAA 2001:db8:dead:beef::2:10"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: subtwodotexampledotcomName, Qtype: dns.TypeNS,
|
||||||
|
Answer: subtwodotexampledotcomNS,
|
||||||
|
Extra: exampledotcomNSAddr6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "myotherservice.subtwo.example.com.", Qtype: dns.TypeAAAA,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.AAAA("myotherservice.subtwo.example.com. 3600 IN AAAA 2001:db8:dead:beef::3:10"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testLookupPTRV6 []test.Case = []test.Case{
|
||||||
|
{
|
||||||
|
Qname: "0.1.0.0.1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("0.1.0.0.1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 3600 IN PTR dns01.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "1.1.0.0.1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("1.1.0.0.1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 3600 IN PTR dns02.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "2.1.0.0.1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("2.1.0.0.1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 3600 IN PTR aservice.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "3.1.0.0.1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("3.1.0.0.1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 3600 IN PTR mail.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "5.1.0.0.1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("5.1.0.0.1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 3600 IN PTR puppet-server-a.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "6.1.0.0.1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("6.1.0.0.1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 3600 IN PTR puppet-server-b.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "7.1.0.0.1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("7.1.0.0.1.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 3600 IN PTR web.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "0.1.0.0.2.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("0.1.0.0.2.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 3600 IN PTR myservice.sub.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "0.1.0.0.3.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa.", Qtype: dns.TypePTR,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.PTR("0.1.0.0.3.0.0.0.0.0.0.0.0.0.0.0.f.e.e.b.d.a.e.d.8.b.d.0.1.0.0.2.ip6.arpa. 3600 IN PTR myotherservice.subtwo.example.com."),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLookupRecords(t *testing.T) {
|
||||||
|
RunTestLookup(t, testLookupRecordV4, testFamilyV4)
|
||||||
|
RunTestLookup(t, testLookupPTRV4, testFamilyV4)
|
||||||
|
RunTestLookup(t, testLookupRecordV6, testFamilyV6)
|
||||||
|
RunTestLookup(t, testLookupPTRV6, testFamilyV6)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
testUnknownRecords []test.Case = []test.Case{
|
||||||
|
{
|
||||||
|
Qname: "noop.com.", Qtype: dns.TypeSOA,
|
||||||
|
Rcode: dns.RcodeNameError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testUnknownRecordsV4 []test.Case = []test.Case{
|
||||||
|
{
|
||||||
|
Qname: "noop.example.com.", Qtype: dns.TypeA,
|
||||||
|
Rcode: dns.RcodeNameError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testUnknownRecordsV6 []test.Case = []test.Case{
|
||||||
|
{
|
||||||
|
Qname: "noop.example.com.", Qtype: dns.TypeAAAA,
|
||||||
|
Rcode: dns.RcodeNameError,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLookupUnknown(t *testing.T) {
|
||||||
|
RunTestLookup(t, testUnknownRecords, testFamilyV4)
|
||||||
|
RunTestLookup(t, testUnknownRecordsV4, testFamilyV4)
|
||||||
|
RunTestLookup(t, testUnknownRecordsV6, testFamilyV6)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOffline(t *testing.T) {
|
||||||
|
netboxdns := NetboxDNS{
|
||||||
|
Next: test.ErrorHandler(),
|
||||||
|
zones: []string{"."},
|
||||||
|
requestClient: &netbox.APIRequestClient{
|
||||||
|
Client: &http.Client{
|
||||||
|
Timeout: defaultHTTPClientTimeout,
|
||||||
|
},
|
||||||
|
NetboxURL: &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: "localhost:9876",
|
||||||
|
Path: testInstanceUrlPath,
|
||||||
|
},
|
||||||
|
Token: testInstanceToken,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tc := test.Case{
|
||||||
|
Qname: exampledotcomName, Qtype: dns.TypeA,
|
||||||
|
}
|
||||||
|
msg := tc.Msg()
|
||||||
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
||||||
|
_, err := netboxdns.ServeDNS(context.Background(), rec, msg)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected connection error, got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnauthorized(t *testing.T) {
|
||||||
|
netboxdns := NetboxDNS{
|
||||||
|
Next: test.ErrorHandler(),
|
||||||
|
zones: []string{"."},
|
||||||
|
requestClient: &netbox.APIRequestClient{
|
||||||
|
Client: &http.Client{
|
||||||
|
Timeout: defaultHTTPClientTimeout,
|
||||||
|
},
|
||||||
|
NetboxURL: &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: testInstanceUrlHost,
|
||||||
|
Path: testInstanceUrlPath,
|
||||||
|
},
|
||||||
|
Token: "noop",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tc := test.Case{
|
||||||
|
Qname: exampledotcomName, Qtype: dns.TypeA,
|
||||||
|
}
|
||||||
|
msg := tc.Msg()
|
||||||
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
||||||
|
_, err := netboxdns.ServeDNS(context.Background(), rec, msg)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected connection error, got none")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFallthrough(t *testing.T) {
|
||||||
|
netboxdns := NetboxDNS{
|
||||||
|
Next: test.ErrorHandler(),
|
||||||
|
zones: []string{exampledotcomName},
|
||||||
|
requestClient: &netbox.APIRequestClient{
|
||||||
|
Client: &http.Client{
|
||||||
|
Timeout: defaultHTTPClientTimeout,
|
||||||
|
},
|
||||||
|
NetboxURL: &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: testInstanceUrlHost,
|
||||||
|
Path: testInstanceUrlPath,
|
||||||
|
},
|
||||||
|
Token: testInstanceToken,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
netboxdns.fall.SetZonesFromArgs([]string{"out.example.com"})
|
||||||
|
tc := test.Case{
|
||||||
|
Qname: "a.out.example.com.", Qtype: dns.TypeA,
|
||||||
|
}
|
||||||
|
msg := tc.Msg()
|
||||||
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
||||||
|
_, err := netboxdns.ServeDNS(context.Background(), rec, msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected fallthrough, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnhandledZone(t *testing.T) {
|
||||||
|
netboxdns := NetboxDNS{
|
||||||
|
Next: test.ErrorHandler(),
|
||||||
|
zones: []string{exampledotcomName},
|
||||||
|
requestClient: &netbox.APIRequestClient{
|
||||||
|
Client: &http.Client{
|
||||||
|
Timeout: defaultHTTPClientTimeout,
|
||||||
|
},
|
||||||
|
NetboxURL: &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: testInstanceUrlHost,
|
||||||
|
Path: testInstanceUrlPath,
|
||||||
|
},
|
||||||
|
Token: testInstanceToken,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tc := test.Case{
|
||||||
|
Qname: "www.example.net.", Qtype: dns.TypeA,
|
||||||
|
}
|
||||||
|
msg := tc.Msg()
|
||||||
|
rec := dnstest.NewRecorder(&test.ResponseWriter{})
|
||||||
|
_, err := netboxdns.ServeDNS(context.Background(), rec, msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
package netboxdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coredns/caddy"
|
||||||
|
"github.com/coredns/coredns/plugin"
|
||||||
|
"github.com/coredns/coredns/plugin/pkg/tls"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tokenFuncMap map[string]func(*caddy.Controller, *NetboxDNS) error
|
||||||
|
|
||||||
|
var tokenFuncs tokenFuncMap
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
tokenFuncs = tokenFuncMap{
|
||||||
|
"fallthrough": parseFallthrough,
|
||||||
|
"timeout": parseTimeout,
|
||||||
|
"tls": parseTLS,
|
||||||
|
"token": parseToken,
|
||||||
|
"url": parseUrl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse netboxdns configuration
|
||||||
|
func Parse(controller *caddy.Controller, netboxdns *NetboxDNS) error {
|
||||||
|
instances := 0
|
||||||
|
for controller.Next() {
|
||||||
|
if instances > 0 {
|
||||||
|
return plugin.ErrOnce
|
||||||
|
}
|
||||||
|
instances++
|
||||||
|
parseZones(controller, netboxdns)
|
||||||
|
if err := parseConfigTokens(controller, netboxdns); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := parseValidate(controller, netboxdns); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPluginURL := netboxdns.requestClient.NetboxURL.JoinPath(
|
||||||
|
"api",
|
||||||
|
"plugins",
|
||||||
|
"netbox-dns",
|
||||||
|
)
|
||||||
|
netboxdns.requestClient.NetboxURL = fullPluginURL
|
||||||
|
|
||||||
|
netboxdns.requestClient.UserAgent = fmt.Sprintf(
|
||||||
|
"coredns plugin %s",
|
||||||
|
pluginName,
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseZones(controller *caddy.Controller, netboxdns *NetboxDNS) {
|
||||||
|
zones := plugin.OriginsFromArgsOrServerBlock(
|
||||||
|
controller.RemainingArgs(),
|
||||||
|
controller.ServerBlockKeys,
|
||||||
|
)
|
||||||
|
if len(zones) > 0 {
|
||||||
|
netboxdns.zones = zones
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseConfigTokens(controller *caddy.Controller, netboxdns *NetboxDNS) error {
|
||||||
|
for controller.NextBlock() {
|
||||||
|
tokenName := controller.Val()
|
||||||
|
tokenFunc, ok := tokenFuncs[tokenName]
|
||||||
|
if !ok {
|
||||||
|
return unknownToken(controller, tokenName)
|
||||||
|
}
|
||||||
|
if err := tokenFunc(controller, netboxdns); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unknownToken(controller *caddy.Controller, unknownToken string) error {
|
||||||
|
expectedTokenString := ""
|
||||||
|
i := 0
|
||||||
|
for tokenName := range tokenFuncs {
|
||||||
|
expectedTokenString += fmt.Sprintf("%q", tokenName)
|
||||||
|
if i+1 < len(tokenFuncs) {
|
||||||
|
expectedTokenString += ", "
|
||||||
|
}
|
||||||
|
if i == len(tokenFuncs)-2 {
|
||||||
|
expectedTokenString += "or "
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return controller.Errf(
|
||||||
|
"unknown token %q; expected %s",
|
||||||
|
unknownToken,
|
||||||
|
expectedTokenString,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFallthrough(
|
||||||
|
controller *caddy.Controller,
|
||||||
|
netboxdns *NetboxDNS,
|
||||||
|
) error {
|
||||||
|
netboxdns.fall.SetZonesFromArgs(controller.RemainingArgs())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTimeout(controller *caddy.Controller, netboxdns *NetboxDNS) error {
|
||||||
|
if !controller.NextArg() {
|
||||||
|
return controller.Err(`no value for "timeout" provided`)
|
||||||
|
}
|
||||||
|
duration, err := time.ParseDuration(controller.Val())
|
||||||
|
if err != nil {
|
||||||
|
return controller.Errf(
|
||||||
|
`there was an error parsing "timeout": %q`,
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
netboxdns.requestClient.Client.Timeout = duration
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTLS(controller *caddy.Controller, netboxdns *NetboxDNS) error {
|
||||||
|
args := controller.RemainingArgs()
|
||||||
|
tlsConfig, err := tls.NewTLSConfigFromArgs(args...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
netboxdns.requestClient.Client.Transport = &http.Transport{
|
||||||
|
TLSClientConfig: tlsConfig,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseToken(controller *caddy.Controller, netboxdns *NetboxDNS) error {
|
||||||
|
if !controller.NextArg() {
|
||||||
|
return controller.Err(`no value for "token" provided`)
|
||||||
|
}
|
||||||
|
netboxdns.requestClient.Token = controller.Val()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseUrl(controller *caddy.Controller, netboxdns *NetboxDNS) error {
|
||||||
|
if !controller.NextArg() {
|
||||||
|
return controller.Err(`no value for "url" provided`)
|
||||||
|
}
|
||||||
|
netboxUrl, err := url.Parse(controller.Val())
|
||||||
|
if err != nil {
|
||||||
|
return controller.Errf(
|
||||||
|
`there was an error parsing "url": %q`,
|
||||||
|
err.Error(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
netboxdns.requestClient.NetboxURL = netboxUrl
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseValidate(controller *caddy.Controller, netboxdns *NetboxDNS) error {
|
||||||
|
tokenEmpty := netboxdns.requestClient.Token == ""
|
||||||
|
urlEmpty := netboxdns.requestClient.NetboxURL == nil ||
|
||||||
|
netboxdns.requestClient.NetboxURL.Host == ""
|
||||||
|
if tokenEmpty && urlEmpty {
|
||||||
|
return controller.Err(
|
||||||
|
`values are required for "token" and "url"`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if tokenEmpty {
|
||||||
|
return controller.Err(`value is required for "token"`)
|
||||||
|
}
|
||||||
|
if urlEmpty {
|
||||||
|
return controller.Err(`value is required for "url"`)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package netboxdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/doubleu-labs/coredns-netbox-plugin-dns/internal/netbox"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
)
|
||||||
|
|
||||||
|
var txtMultiValueRegexp *regexp.Regexp
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
txtMultiValueRegexp = regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func recordsToRR(records []netbox.Record) ([]dns.RR, error) {
|
||||||
|
out := make([]dns.RR, 0, len(records))
|
||||||
|
for _, record := range records {
|
||||||
|
qtype := dns.StringToType[record.Type]
|
||||||
|
switch qtype {
|
||||||
|
case dns.TypeTXT:
|
||||||
|
out = append(out, recordToTXT(record))
|
||||||
|
default:
|
||||||
|
rrStr := fmt.Sprintf(
|
||||||
|
"%s %d IN %s %s",
|
||||||
|
record.FQDN,
|
||||||
|
*record.TTL,
|
||||||
|
record.Type,
|
||||||
|
record.Value,
|
||||||
|
)
|
||||||
|
rr, err := dns.NewRR(rrStr)
|
||||||
|
if err != nil {
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
out = append(out, rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func recordToTXT(record netbox.Record) *dns.TXT {
|
||||||
|
txt := make([]string, 0)
|
||||||
|
if strings.HasPrefix(record.Value, `"`) {
|
||||||
|
values := txtMultiValueRegexp.FindAllString(record.Value, -1)
|
||||||
|
for i := range values {
|
||||||
|
values[i] = strings.Trim(values[i], `"`)
|
||||||
|
values[i] = strings.ReplaceAll(values[i], "\\r\\n", "")
|
||||||
|
values[i] = strings.ReplaceAll(values[i], "\\n", "")
|
||||||
|
values[i] = strings.TrimSpace(values[i])
|
||||||
|
if values[i] != "" {
|
||||||
|
txt = append(txt, values[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
txt = append(txt, record.Value)
|
||||||
|
}
|
||||||
|
return &dns.TXT{
|
||||||
|
Hdr: dns.RR_Header{
|
||||||
|
Name: record.FQDN,
|
||||||
|
Ttl: *record.TTL,
|
||||||
|
Class: dns.ClassINET,
|
||||||
|
Rrtype: dns.TypeTXT,
|
||||||
|
},
|
||||||
|
Txt: txt,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterRRByType(rrs []dns.RR, recordType uint16) []dns.RR {
|
||||||
|
out := make([]dns.RR, 0)
|
||||||
|
for _, rr := range rrs {
|
||||||
|
if rr.Header().Rrtype == recordType {
|
||||||
|
out = append(out, rr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package netboxdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/coredns/caddy"
|
||||||
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
|
"github.com/coredns/coredns/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
plugin.Register(pluginName, setup)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup(controller *caddy.Controller) error {
|
||||||
|
netboxdns := NewNetboxDNS()
|
||||||
|
if err := Parse(controller, netboxdns); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dnsserver.GetConfig(controller).AddPlugin(
|
||||||
|
func(next plugin.Handler) plugin.Handler {
|
||||||
|
netboxdns.Next = next
|
||||||
|
return netboxdns
|
||||||
|
},
|
||||||
|
)
|
||||||
|
logger.Info("successfully started netboxdns")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+199
@@ -0,0 +1,199 @@
|
|||||||
|
package netboxdns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coredns/caddy"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SetupTest struct {
|
||||||
|
Name string
|
||||||
|
Corefile string
|
||||||
|
WantErr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var setupTests []SetupTest = []SetupTest{
|
||||||
|
{
|
||||||
|
"no configuration",
|
||||||
|
`netboxdns`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"unknown token",
|
||||||
|
`netboxdns {
|
||||||
|
noop
|
||||||
|
}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no netbox token specified",
|
||||||
|
`netboxdns {
|
||||||
|
url http://localhost:9999/
|
||||||
|
}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no value for netbox token",
|
||||||
|
`netboxdns {
|
||||||
|
url http://localhost:9999/
|
||||||
|
token
|
||||||
|
}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no netbox url specified",
|
||||||
|
`netboxdns {
|
||||||
|
token sometoken
|
||||||
|
}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no value for netbox url",
|
||||||
|
`netboxdns {
|
||||||
|
token sometoken
|
||||||
|
url
|
||||||
|
}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minimum valid configuration",
|
||||||
|
`netboxdns {
|
||||||
|
token sometoken
|
||||||
|
url http://localhost:9999/
|
||||||
|
}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid netbox url value",
|
||||||
|
`netboxdns {
|
||||||
|
token sometoken
|
||||||
|
url "http://local host:9999/"
|
||||||
|
}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"multiple configurations",
|
||||||
|
`netboxdns {
|
||||||
|
token sometoken
|
||||||
|
url http://localhost:9999/
|
||||||
|
}
|
||||||
|
netboxdns {
|
||||||
|
token sometoken
|
||||||
|
url http://localhost:9999/
|
||||||
|
}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"configuration with responsible zone",
|
||||||
|
`netboxdns example.com {
|
||||||
|
token sometoken
|
||||||
|
url http://localhost:9999/
|
||||||
|
}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"no value for timeout specified",
|
||||||
|
`netboxdns {
|
||||||
|
token sometoken
|
||||||
|
url http://localhost:9999/
|
||||||
|
timeout
|
||||||
|
}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minimum configuration with timeout",
|
||||||
|
`netboxdns {
|
||||||
|
token sometoken
|
||||||
|
url http://localhost:9999/
|
||||||
|
timeout 10s
|
||||||
|
}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid timeout",
|
||||||
|
`netboxdns {
|
||||||
|
token sometoken
|
||||||
|
url http://localhost:9999/
|
||||||
|
timeout 10g
|
||||||
|
}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minimum configuration fallthrough all zones",
|
||||||
|
`netboxdns {
|
||||||
|
token sometoken
|
||||||
|
url http://localhost:9999/
|
||||||
|
fallthrough
|
||||||
|
}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minimum configuration fallthrough specified zone",
|
||||||
|
`netboxdns {
|
||||||
|
token sometoken
|
||||||
|
url http://localhost:9999/
|
||||||
|
fallthrough example.net
|
||||||
|
}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minimum configuration tls system ca",
|
||||||
|
`netboxdns {
|
||||||
|
token sometoken
|
||||||
|
url http://localhost:9999/
|
||||||
|
tls
|
||||||
|
}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minimum configuration tls nonexistant file",
|
||||||
|
`netboxdns {
|
||||||
|
token sometoken
|
||||||
|
url http://localhost:9999/
|
||||||
|
tls noop.pem
|
||||||
|
}`,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minimum configuration tls private ca",
|
||||||
|
`netboxdns {
|
||||||
|
token sometoken
|
||||||
|
url http://localhost:9999/
|
||||||
|
tls .testing/tls/ca.pem
|
||||||
|
}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minimum configuration tls client auth",
|
||||||
|
`netboxdns {
|
||||||
|
token sometoken
|
||||||
|
url http://localhost:9999/
|
||||||
|
tls .testing/tls/client.pem .testing/tls/client-key.pem
|
||||||
|
}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minimum configuration tls client auth private ca",
|
||||||
|
`netboxdns {
|
||||||
|
token sometoken
|
||||||
|
url http://localhost:9999/
|
||||||
|
tls .testing/tls/client.pem .testing/tls/client-key.pem .testing/tls/ca.pem
|
||||||
|
}`,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetup(t *testing.T) {
|
||||||
|
for _, tt := range setupTests {
|
||||||
|
t.Run(tt.Name, func(t *testing.T) {
|
||||||
|
controller := caddy.NewTestController("dns", tt.Corefile)
|
||||||
|
if err := setup(controller); (err != nil) != tt.WantErr {
|
||||||
|
t.Errorf(
|
||||||
|
"setup error: %v, wanterr: %t",
|
||||||
|
err,
|
||||||
|
tt.WantErr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user