Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22c41911ce | ||
|
|
b7d9f44765 | ||
|
|
c6826aad31 | ||
|
|
f448431675 | ||
|
|
0d1ca5cad7 | ||
|
|
48bfe812c0 | ||
|
|
dc1a8f54b1 | ||
|
|
e0f9745182 | ||
|
|
d20fae37c9 | ||
|
|
0450268bec | ||
|
|
34344b1b35 | ||
|
|
1c4e1e55e4 | ||
|
|
3e0ee478b7 | ||
|
|
f90ef442a3 | ||
|
|
157241994f | ||
|
|
1405e6ab47 |
@@ -1,43 +0,0 @@
|
|||||||
name: goreleaser
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
goreleaser:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: stable
|
|
||||||
- name: Import GPG key
|
|
||||||
id: import_gpg
|
|
||||||
uses: crazy-max/ghaction-import-gpg@v6
|
|
||||||
with:
|
|
||||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
|
||||||
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
- name: Run GoReleaser
|
|
||||||
uses: goreleaser/goreleaser-action@v6
|
|
||||||
|
|
||||||
with:
|
|
||||||
distribution: goreleaser
|
|
||||||
version: latest
|
|
||||||
args: release --clean
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }}
|
|
||||||
+1
-15
@@ -1,7 +1,7 @@
|
|||||||
builds:
|
builds:
|
||||||
- binary: acme-dns
|
- binary: acme-dns
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=1
|
||||||
goos:
|
goos:
|
||||||
- linux
|
- linux
|
||||||
goarch:
|
goarch:
|
||||||
@@ -19,17 +19,3 @@ archives:
|
|||||||
|
|
||||||
signs:
|
signs:
|
||||||
- artifacts: checksum
|
- artifacts: checksum
|
||||||
args: ["--batch", "-u", "{{ .Env.GPG_FINGERPRINT }}", "--output", "${signature}", "--detach-sign", "${artifact}"]
|
|
||||||
|
|
||||||
dockers:
|
|
||||||
- image_templates:
|
|
||||||
- "joohoi/acme-dns:{{ .Tag }}"
|
|
||||||
- "joohoi/acme-dns:latest"
|
|
||||||
dockerfile: Dockerfile.release
|
|
||||||
build_flag_templates:
|
|
||||||
- "--pull"
|
|
||||||
- "--label=org.opencontainers.image.created={{.Date}}"
|
|
||||||
- "--label=org.opencontainers.image.name={{.ProjectName}}"
|
|
||||||
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
|
|
||||||
- "--label=org.opencontainers.image.version={{.Version}}"
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
## v2.0
|
|
||||||
- Update goreleaser configuration and add a GitHub action to build a release on new version tags (#395)
|
|
||||||
- Huge refactoring and modernization (#325)
|
|
||||||
|
|
||||||
## v1.1
|
|
||||||
- Add timeout to golangci job (#369)
|
|
||||||
- Update deps to support go 1.23 (#368)
|
|
||||||
- Bump dependencies (#334)
|
|
||||||
|
|
||||||
## v1.0
|
|
||||||
- New
|
|
||||||
- Refactoring of the codebase to something more robust
|
|
||||||
- Changed
|
|
||||||
- Updated dependencies
|
|
||||||
- v0.8
|
|
||||||
- NOTE: configuration option: "api_domain" deprecated!
|
|
||||||
- New
|
|
||||||
- Automatic HTTP API certificate provisioning using DNS challenges making acme-dns able to acquire certificates even with HTTP api not being accessible from public internet.
|
|
||||||
- Configuration value for "tls": "letsencryptstaging". Setting it will help you to debug possible issues with HTTP API certificate acquiring process. This is the new default value.
|
|
||||||
- Changed
|
|
||||||
- Fixed: EDNS0 support
|
|
||||||
- Migrated from autocert to [certmagic](https://github.com/mholt/certmagic) for HTTP API certificate handling
|
|
||||||
- v0.7.2
|
|
||||||
- Changed
|
|
||||||
- Fixed: Regression error of not being able to answer to incoming random-case requests.
|
|
||||||
- Fixed: SOA record added to a correct header field in NXDOMAIN responses.
|
|
||||||
- v0.7.1
|
|
||||||
- Changed
|
|
||||||
- Fixed: SOA record correctly added to the TCP DNS server when using both, UDP and TCP servers.
|
|
||||||
- v0.7
|
|
||||||
- New
|
|
||||||
- Added an endpoint to perform health checks
|
|
||||||
- Changed
|
|
||||||
- A new protocol selection for DNS server "both", that binds both - UDP and TCP ports.
|
|
||||||
- Refactored DNS server internals.
|
|
||||||
- Handle some aspects of DNS spec better.
|
|
||||||
- v0.6
|
|
||||||
- New
|
|
||||||
- Command line flag `-c` to specify location of config file.
|
|
||||||
- Proper refusal of dynamic update requests.
|
|
||||||
- Release signing
|
|
||||||
- Changed
|
|
||||||
- Better error messages for goroutines
|
|
||||||
- v0.5
|
|
||||||
- New
|
|
||||||
- Configurable certificate cache directory
|
|
||||||
- Changed
|
|
||||||
- Process wide umask to ensure created files are only readable by the user running acme-dns
|
|
||||||
- Replaced package that handles UUIDs because of a flaw in the original package
|
|
||||||
- Updated dependencies
|
|
||||||
- Better error messages
|
|
||||||
- v0.4 Clear error messages for bad TXT record content, proper handling of static CNAME records, fixed IP address parsing from the request, added option to disable registration endpoint in the configuration.
|
|
||||||
- v0.3.2 Dockerfile was fixed for users using autocert feature
|
|
||||||
- v0.3.1 Added goreleaser for distributing binary builds of the releases
|
|
||||||
- v0.3 Changed autocert to use HTTP-01 challenges, as TLS-SNI is disabled by Let's Encrypt
|
|
||||||
- v0.2 Now powered by httprouter, support wildcard certificates, Docker images
|
|
||||||
- v0.1 Initial release
|
|
||||||
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
FROM golang:alpine AS builder
|
FROM golang:alpine AS builder
|
||||||
LABEL maintainer="joona@kuori.org"
|
LABEL maintainer="joona@kuori.org"
|
||||||
|
|
||||||
RUN apk add --update git
|
RUN apk add --update gcc musl-dev git
|
||||||
|
|
||||||
ENV GOPATH /tmp/buildcache
|
ENV GOPATH /tmp/buildcache
|
||||||
RUN git clone https://github.com/joohoi/acme-dns /tmp/acme-dns
|
RUN git clone https://github.com/joohoi/acme-dns /tmp/acme-dns
|
||||||
WORKDIR /tmp/acme-dns
|
WORKDIR /tmp/acme-dns
|
||||||
RUN CGO_ENABLED=0 go build
|
RUN CGO_ENABLED=1 go build
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
FROM alpine:latest
|
|
||||||
|
|
||||||
RUN apk --no-cache add ca-certificates && update-ca-certificates
|
|
||||||
RUN mkdir -p /etc/acme-dns
|
|
||||||
RUN mkdir -p /var/lib/acme-dns
|
|
||||||
|
|
||||||
COPY acme-dns /usr/local/bin/acme-dns
|
|
||||||
|
|
||||||
VOLUME ["/etc/acme-dns", "/var/lib/acme-dns"]
|
|
||||||
ENTRYPOINT ["acme-dns"]
|
|
||||||
EXPOSE 53 80 443
|
|
||||||
EXPOSE 53/udp
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2016-2026 Joona Hoikkala
|
Copyright (c) 2016 Joona Hoikkala
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -338,6 +338,55 @@ use for the renewal.
|
|||||||
- Generic client library in Go: [https://github.com/cpu/goacmedns](https://github.com/cpu/goacmedns)
|
- Generic client library in Go: [https://github.com/cpu/goacmedns](https://github.com/cpu/goacmedns)
|
||||||
|
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
- v1.0
|
||||||
|
- New
|
||||||
|
- Refactoring of the codebase to something more robust
|
||||||
|
- Changed
|
||||||
|
- Updated dependencies
|
||||||
|
- v0.8
|
||||||
|
- NOTE: configuration option: "api_domain" deprecated!
|
||||||
|
- New
|
||||||
|
- Automatic HTTP API certificate provisioning using DNS challenges making acme-dns able to acquire certificates even with HTTP api not being accessible from public internet.
|
||||||
|
- Configuration value for "tls": "letsencryptstaging". Setting it will help you to debug possible issues with HTTP API certificate acquiring process. This is the new default value.
|
||||||
|
- Changed
|
||||||
|
- Fixed: EDNS0 support
|
||||||
|
- Migrated from autocert to [certmagic](https://github.com/mholt/certmagic) for HTTP API certificate handling
|
||||||
|
- v0.7.2
|
||||||
|
- Changed
|
||||||
|
- Fixed: Regression error of not being able to answer to incoming random-case requests.
|
||||||
|
- Fixed: SOA record added to a correct header field in NXDOMAIN responses.
|
||||||
|
- v0.7.1
|
||||||
|
- Changed
|
||||||
|
- Fixed: SOA record correctly added to the TCP DNS server when using both, UDP and TCP servers.
|
||||||
|
- v0.7
|
||||||
|
- New
|
||||||
|
- Added an endpoint to perform health checks
|
||||||
|
- Changed
|
||||||
|
- A new protocol selection for DNS server "both", that binds both - UDP and TCP ports.
|
||||||
|
- Refactored DNS server internals.
|
||||||
|
- Handle some aspects of DNS spec better.
|
||||||
|
- v0.6
|
||||||
|
- New
|
||||||
|
- Command line flag `-c` to specify location of config file.
|
||||||
|
- Proper refusal of dynamic update requests.
|
||||||
|
- Release signing
|
||||||
|
- Changed
|
||||||
|
- Better error messages for goroutines
|
||||||
|
- v0.5
|
||||||
|
- New
|
||||||
|
- Configurable certificate cache directory
|
||||||
|
- Changed
|
||||||
|
- Process wide umask to ensure created files are only readable by the user running acme-dns
|
||||||
|
- Replaced package that handles UUIDs because of a flaw in the original package
|
||||||
|
- Updated dependencies
|
||||||
|
- Better error messages
|
||||||
|
- v0.4 Clear error messages for bad TXT record content, proper handling of static CNAME records, fixed IP address parsing from the request, added option to disable registration endpoint in the configuration.
|
||||||
|
- v0.3.2 Dockerfile was fixed for users using autocert feature
|
||||||
|
- v0.3.1 Added goreleaser for distributing binary builds of the releases
|
||||||
|
- v0.3 Changed autocert to use HTTP-01 challenges, as TLS-SNI is disabled by Let's Encrypt
|
||||||
|
- v0.2 Now powered by httprouter, support wildcard certificates, Docker images
|
||||||
|
- v0.1 Initial release
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
@@ -352,4 +401,4 @@ If you have an idea for improvement, please open an new issue or feel free to wr
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
acme-dns is released under the [MIT License](https://www.opensource.org/licenses/MIT).
|
acme-dns is released under the [MIT License](http://www.opensource.org/licenses/MIT).
|
||||||
|
|||||||
@@ -18,12 +18,6 @@ records = [
|
|||||||
# specify that auth.example.org will resolve any *.auth.example.org records
|
# specify that auth.example.org will resolve any *.auth.example.org records
|
||||||
"auth.example.org. NS auth.example.org.",
|
"auth.example.org. NS auth.example.org.",
|
||||||
]
|
]
|
||||||
# path to cache SOA serial
|
|
||||||
serialpath = "soa-serial.save"
|
|
||||||
# slaves to notify on update and allowed to request AXFR
|
|
||||||
slaves = [
|
|
||||||
# "10.5.1.1"
|
|
||||||
]
|
|
||||||
# debug messages from CORS etc
|
# debug messages from CORS etc
|
||||||
debug = false
|
debug = false
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ type AcmednsDB interface {
|
|||||||
Register(cidrslice Cidrslice) (ACMETxt, error)
|
Register(cidrslice Cidrslice) (ACMETxt, error)
|
||||||
GetByUsername(uuid.UUID) (ACMETxt, error)
|
GetByUsername(uuid.UUID) (ACMETxt, error)
|
||||||
GetTXTForDomain(string) ([]string, error)
|
GetTXTForDomain(string) ([]string, error)
|
||||||
GetTXTForAllDomains() ([]TXTRecord, error)
|
|
||||||
Update(ACMETxtPost) error
|
Update(ACMETxtPost) error
|
||||||
GetBackend() *sql.DB
|
GetBackend() *sql.DB
|
||||||
SetBackend(*sql.DB)
|
SetBackend(*sql.DB)
|
||||||
@@ -22,10 +21,4 @@ type AcmednsNS interface {
|
|||||||
SetOwnAuthKey(key string)
|
SetOwnAuthKey(key string)
|
||||||
SetNotifyStartedFunc(func())
|
SetNotifyStartedFunc(func())
|
||||||
ParseRecords()
|
ParseRecords()
|
||||||
BumpSerial() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type TXTRecord struct {
|
|
||||||
Subdomain string
|
|
||||||
Value string
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,6 @@ type general struct {
|
|||||||
Nsadmin string
|
Nsadmin string
|
||||||
Debug bool
|
Debug bool
|
||||||
StaticRecords []string `toml:"records"`
|
StaticRecords []string `toml:"records"`
|
||||||
Serialpath string
|
|
||||||
SlaveHosts []string `toml:"slaves"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type dbsettings struct {
|
type dbsettings struct {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ type AcmednsAPI struct {
|
|||||||
DB acmedns.AcmednsDB
|
DB acmedns.AcmednsDB
|
||||||
Logger *zap.SugaredLogger
|
Logger *zap.SugaredLogger
|
||||||
errChan chan error
|
errChan chan error
|
||||||
dnsServers []acmedns.AcmednsNS
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Init(config *acmedns.AcmeDnsConfig, db acmedns.AcmednsDB, logger *zap.SugaredLogger, errChan chan error) AcmednsAPI {
|
func Init(config *acmedns.AcmeDnsConfig, db acmedns.AcmednsDB, logger *zap.SugaredLogger, errChan chan error) AcmednsAPI {
|
||||||
@@ -28,8 +27,6 @@ func Init(config *acmedns.AcmeDnsConfig, db acmedns.AcmednsDB, logger *zap.Sugar
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *AcmednsAPI) Start(dnsservers []acmedns.AcmednsNS) {
|
func (a *AcmednsAPI) Start(dnsservers []acmedns.AcmednsNS) {
|
||||||
//we need the dnsservers later to bump serial
|
|
||||||
a.dnsServers = dnsservers
|
|
||||||
var err error
|
var err error
|
||||||
//TODO: do we want to debug log the HTTP server?
|
//TODO: do we want to debug log the HTTP server?
|
||||||
stderrorlog, err := zap.NewStdLogAt(a.Logger.Desugar(), zap.ErrorLevel)
|
stderrorlog, err := zap.NewStdLogAt(a.Logger.Desugar(), zap.ErrorLevel)
|
||||||
|
|||||||
@@ -49,10 +49,6 @@ func (a *AcmednsAPI) webUpdatePost(w http.ResponseWriter, r *http.Request, _ htt
|
|||||||
upd = []byte("{\"txt\": \"" + atxt.Value + "\"}")
|
upd = []byte("{\"txt\": \"" + atxt.Value + "\"}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, s := range a.dnsServers {
|
|
||||||
//bump SOA serial on update (and notify slaves if configured)
|
|
||||||
s.BumpSerial()
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(updStatus)
|
w.WriteHeader(updStatus)
|
||||||
_, _ = w.Write(upd)
|
_, _ = w.Write(upd)
|
||||||
|
|||||||
@@ -263,50 +263,6 @@ func (d *acmednsdb) GetByUsername(u uuid.UUID) (acmedns.ACMETxt, error) {
|
|||||||
return acmedns.ACMETxt{}, fmt.Errorf("user not found: %s", u.String())
|
return acmedns.ACMETxt{}, fmt.Errorf("user not found: %s", u.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *acmednsdb) GetTXTForAllDomains() ([]acmedns.TXTRecord, error) {
|
|
||||||
d.Mutex.Lock()
|
|
||||||
defer d.Mutex.Unlock()
|
|
||||||
|
|
||||||
var txts []acmedns.TXTRecord
|
|
||||||
|
|
||||||
getSQL := `
|
|
||||||
SELECT Subdomain, Value FROM txt
|
|
||||||
`
|
|
||||||
if d.Config.Database.Engine == "sqlite" {
|
|
||||||
getSQL = getSQLiteStmt(getSQL)
|
|
||||||
}
|
|
||||||
|
|
||||||
sm, err := d.DB.Prepare(getSQL)
|
|
||||||
if err != nil {
|
|
||||||
return txts, err
|
|
||||||
}
|
|
||||||
defer sm.Close()
|
|
||||||
|
|
||||||
rows, err := sm.Query()
|
|
||||||
if err != nil {
|
|
||||||
return txts, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
var subdomain string
|
|
||||||
var value string
|
|
||||||
|
|
||||||
err = rows.Scan(&subdomain, &value)
|
|
||||||
if err != nil {
|
|
||||||
return txts, err
|
|
||||||
}
|
|
||||||
|
|
||||||
d.Logger.Debugw("GetTXTForAllDomains() TXT Record:", subdomain, value)
|
|
||||||
txts = append(txts, acmedns.TXTRecord{
|
|
||||||
Subdomain: subdomain,
|
|
||||||
Value: value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return txts, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *acmednsdb) GetTXTForDomain(domain string) ([]string, error) {
|
func (d *acmednsdb) GetTXTForDomain(domain string) ([]string, error) {
|
||||||
d.Mutex.Lock()
|
d.Mutex.Lock()
|
||||||
defer d.Mutex.Unlock()
|
defer d.Mutex.Unlock()
|
||||||
|
|||||||
@@ -2,48 +2,12 @@ package nameserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (n *Nameserver) handleRequest(w dns.ResponseWriter, r *dns.Msg) {
|
func (n *Nameserver) handleRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||||
|
|
||||||
if len(r.Question) == 1 {
|
|
||||||
q := r.Question[0]
|
|
||||||
if q.Qtype == dns.TypeAXFR || q.Qtype == dns.TypeIXFR { // Get remote IP
|
|
||||||
remoteIP, _, err := net.SplitHostPort(w.RemoteAddr().String())
|
|
||||||
if err != nil {
|
|
||||||
n.Logger.Errorw("Failed to parse remote address", "err", err)
|
|
||||||
m := new(dns.Msg)
|
|
||||||
m.SetReply(r)
|
|
||||||
m.Rcode = dns.RcodeRefused
|
|
||||||
_ = w.WriteMsg(m)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if remote IP is in slave list
|
|
||||||
allowed := false
|
|
||||||
for _, slave := range n.Config.General.SlaveHosts {
|
|
||||||
if remoteIP == slave {
|
|
||||||
allowed = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !allowed {
|
|
||||||
n.Logger.Warnw("AXFR/IXFR request denied", "remote", remoteIP)
|
|
||||||
m := new(dns.Msg)
|
|
||||||
m.SetReply(r)
|
|
||||||
m.Rcode = dns.RcodeRefused
|
|
||||||
_ = w.WriteMsg(m)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n.handleAXFR(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m := new(dns.Msg)
|
m := new(dns.Msg)
|
||||||
m.SetReply(r)
|
m.SetReply(r)
|
||||||
// handle edns0
|
// handle edns0
|
||||||
@@ -107,9 +71,6 @@ func (n *Nameserver) answer(q dns.Question) ([]dns.RR, int, bool, error) {
|
|||||||
r = append(r, txtRRs...)
|
r = append(r, txtRRs...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if q.Qtype == dns.TypeSOA {
|
|
||||||
r = append(r, n.SOA)
|
|
||||||
}
|
|
||||||
if len(r) > 0 {
|
if len(r) > 0 {
|
||||||
// Make sure that we return NOERROR if there were dynamic records for the domain
|
// Make sure that we return NOERROR if there were dynamic records for the domain
|
||||||
rcode = dns.RcodeSuccess
|
rcode = dns.RcodeSuccess
|
||||||
@@ -197,77 +158,3 @@ func (n *Nameserver) getRecord(name string, qtype uint16) ([]dns.RR, error) {
|
|||||||
}
|
}
|
||||||
return rr, nil
|
return rr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nameserver) handleAXFR(w dns.ResponseWriter, r *dns.Msg) {
|
|
||||||
|
|
||||||
if len(r.Question) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
zone := dns.Fqdn(r.Question[0].Name)
|
|
||||||
|
|
||||||
records, ok := n.Domains[zone]
|
|
||||||
if !ok {
|
|
||||||
m := new(dns.Msg)
|
|
||||||
m.SetRcode(r, dns.RcodeNameError)
|
|
||||||
_ = w.WriteMsg(m)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// AXFR muss über Transfer laufen
|
|
||||||
tr := new(dns.Transfer)
|
|
||||||
|
|
||||||
c := make(chan *dns.Envelope)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer close(c)
|
|
||||||
|
|
||||||
var rr []dns.RR
|
|
||||||
|
|
||||||
// Start SOA
|
|
||||||
rr = append(rr, n.SOA)
|
|
||||||
|
|
||||||
// NS
|
|
||||||
rr = append(rr, records.NS...)
|
|
||||||
|
|
||||||
// Andere Records
|
|
||||||
// rr = append(rr, filterSOA(records.Records)...)
|
|
||||||
rr = append(rr, records.Records...)
|
|
||||||
|
|
||||||
// TXT Records nur für diese Zone!
|
|
||||||
txtRecords, err := n.DB.GetTXTForAllDomains()
|
|
||||||
if err == nil {
|
|
||||||
for _, rec := range txtRecords {
|
|
||||||
if rec.Value == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fqdn := dns.Fqdn(rec.Subdomain + "." + zone)
|
|
||||||
|
|
||||||
txtRR := &dns.TXT{
|
|
||||||
Hdr: dns.RR_Header{
|
|
||||||
Name: fqdn,
|
|
||||||
Rrtype: dns.TypeTXT,
|
|
||||||
Class: dns.ClassINET,
|
|
||||||
Ttl: 1,
|
|
||||||
},
|
|
||||||
Txt: []string{rec.Value},
|
|
||||||
}
|
|
||||||
|
|
||||||
rr = append(rr, txtRR)
|
|
||||||
|
|
||||||
n.Logger.Debugw("handleAXFR TXT Record", "subdomain", rec.Subdomain, "value", rec.Value, "fqdn", fqdn)
|
|
||||||
rr = append(rr, txtRR)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
n.Logger.Errorw("Failed to get TXT records for AXFR", "error", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// End SOA
|
|
||||||
rr = append(rr, n.SOA)
|
|
||||||
|
|
||||||
c <- &dns.Envelope{RR: rr}
|
|
||||||
}()
|
|
||||||
|
|
||||||
_ = tr.Out(w, r, c)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import (
|
|||||||
// Records is a slice of ResourceRecords
|
// Records is a slice of ResourceRecords
|
||||||
type Records struct {
|
type Records struct {
|
||||||
Records []dns.RR
|
Records []dns.RR
|
||||||
NS []dns.RR
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Nameserver struct {
|
type Nameserver struct {
|
||||||
@@ -24,7 +23,7 @@ type Nameserver struct {
|
|||||||
Server *dns.Server
|
Server *dns.Server
|
||||||
OwnDomain string
|
OwnDomain string
|
||||||
NotifyStartedFunc func()
|
NotifyStartedFunc func()
|
||||||
SOA *dns.SOA
|
SOA dns.RR
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
personalAuthKey string
|
personalAuthKey string
|
||||||
Domains map[string]Records
|
Domains map[string]Records
|
||||||
|
|||||||
+10
-109
@@ -2,64 +2,12 @@ package nameserver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadSerial(path string) (uint32, error) {
|
|
||||||
data, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return 0, nil // first start
|
|
||||||
}
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
s := strings.TrimSpace(string(data))
|
|
||||||
val, err := strconv.ParseUint(s, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return uint32(val), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func saveSerial(path string, serial uint32) error {
|
|
||||||
tmp := path + ".tmp"
|
|
||||||
|
|
||||||
data := []byte(fmt.Sprintf("%d\n", serial))
|
|
||||||
|
|
||||||
// write temp file
|
|
||||||
if err := os.WriteFile(tmp, data, 0644); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// atomic replace
|
|
||||||
return os.Rename(tmp, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
func nextSerial(old uint32) uint32 {
|
|
||||||
today := time.Now().Format("20060102")
|
|
||||||
|
|
||||||
oldStr := fmt.Sprintf("%d", old)
|
|
||||||
|
|
||||||
if strings.HasPrefix(oldStr, today) {
|
|
||||||
return old + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
newSerial, _ := strconv.Atoi(today + "00")
|
|
||||||
|
|
||||||
if uint32(newSerial) <= old {
|
|
||||||
return old + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return uint32(newSerial)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseRecords parses a slice of DNS record string
|
// ParseRecords parses a slice of DNS record string
|
||||||
func (n *Nameserver) ParseRecords() {
|
func (n *Nameserver) ParseRecords() {
|
||||||
for _, v := range n.Config.General.StaticRecords {
|
for _, v := range n.Config.General.StaticRecords {
|
||||||
@@ -74,72 +22,25 @@ func (n *Nameserver) ParseRecords() {
|
|||||||
n.appendRR(rr)
|
n.appendRR(rr)
|
||||||
}
|
}
|
||||||
// Create serial
|
// Create serial
|
||||||
serial, err := loadSerial(n.Config.General.Serialpath)
|
serial := time.Now().Format("2006010215")
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
n.Logger.Errorw("Could not load temp serial",
|
|
||||||
"error", err.Error())
|
|
||||||
}
|
|
||||||
if serial == 0 {
|
|
||||||
serial = uint32(time.Now().Unix())
|
|
||||||
}
|
|
||||||
// Add SOA
|
// Add SOA
|
||||||
//Refresh = 30s → Slaves fragen alle 30s nach Änderungen
|
SOAstring := fmt.Sprintf("%s. SOA %s. %s. %s 28800 7200 604800 86400", strings.ToLower(n.Config.General.Domain), strings.ToLower(n.Config.General.Nsname), strings.ToLower(n.Config.General.Nsadmin), serial)
|
||||||
//Retry = 10s → Wenn Master nicht erreichbar, probiert der Slave alle 10s erneut
|
soarr, err := dns.NewRR(SOAstring)
|
||||||
//Expire = 604800s (1w) → Wie lange der Slave die Zone noch behält, falls Master ausfällt
|
if err != nil {
|
||||||
//Minimum TTL = 20s → Resolver cachen die TXT-Einträge nur kurz
|
n.Logger.Errorw("Error while adding SOA record",
|
||||||
//SOAstring := fmt.Sprintf("%s. SOA %s. %s. %s 5 10 604800 20", strings.ToLower(n.Config.General.Domain), strings.ToLower(n.Config.General.Nsname), strings.ToLower(n.Config.General.Nsadmin), serial)
|
"error", err.Error(),
|
||||||
n.SOA = &dns.SOA{
|
"soa", SOAstring)
|
||||||
Hdr: dns.RR_Header{
|
|
||||||
Name: dns.Fqdn(n.Config.General.Domain),
|
|
||||||
Rrtype: dns.TypeSOA,
|
|
||||||
Class: dns.ClassINET,
|
|
||||||
Ttl: 5,
|
|
||||||
},
|
|
||||||
Ns: dns.Fqdn(n.Config.General.Nsname),
|
|
||||||
Mbox: dns.Fqdn(n.Config.General.Nsadmin),
|
|
||||||
Serial: serial,
|
|
||||||
Refresh: 5,
|
|
||||||
Retry: 10,
|
|
||||||
Expire: 604800,
|
|
||||||
Minttl: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendNotify(zone string, slaveAddr string) error {
|
|
||||||
m := new(dns.Msg)
|
|
||||||
m.SetNotify(dns.Fqdn(zone)) // Set opcode to NOTIFY
|
|
||||||
m.Authoritative = true // Must be authoritative
|
|
||||||
|
|
||||||
c := new(dns.Client)
|
|
||||||
_, _, err := c.Exchange(m, slaveAddr)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Nameserver) BumpSerial() error {
|
|
||||||
n.mu.Lock()
|
|
||||||
defer n.mu.Unlock()
|
|
||||||
n.SOA.Serial = nextSerial(n.SOA.Serial)
|
|
||||||
|
|
||||||
for _, slave := range n.Config.General.SlaveHosts {
|
|
||||||
slave := slave + ":53"
|
|
||||||
if err := sendNotify(n.SOA.Hdr.Name, slave); err != nil {
|
|
||||||
n.Logger.Errorw("Failed to notify slave", "slave", slave, "err", err)
|
|
||||||
} else {
|
} else {
|
||||||
n.Logger.Debugw("Notify send to slave", "slave", slave)
|
n.appendRR(soarr)
|
||||||
|
n.SOA = soarr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return saveSerial(n.Config.General.Serialpath, n.SOA.Serial)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Nameserver) appendRR(rr dns.RR) {
|
func (n *Nameserver) appendRR(rr dns.RR) {
|
||||||
addDomain := rr.Header().Name
|
addDomain := rr.Header().Name
|
||||||
_, ok := n.Domains[addDomain]
|
_, ok := n.Domains[addDomain]
|
||||||
if !ok {
|
if !ok {
|
||||||
n.Domains[addDomain] = Records{
|
n.Domains[addDomain] = Records{[]dns.RR{rr}}
|
||||||
Records: []dns.RR{rr}, // initialisiere Records
|
|
||||||
NS: []dns.RR{}, // leeres NS-Slice, sonst Fehler
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
drecs := n.Domains[addDomain]
|
drecs := n.Domains[addDomain]
|
||||||
drecs.Records = append(drecs.Records, rr)
|
drecs.Records = append(drecs.Records, rr)
|
||||||
|
|||||||
Reference in New Issue
Block a user