Compare commits

..
12 Commits
Author SHA1 Message Date
Joona HoikkalaandGitHub 20411b650f Update changelog and prepare for release v0.6 (#125) 2018-10-31 15:13:29 +02:00
Joona HoikkalaandGitHub 3bb130d055 Refuse dynamic updates properly (#124) 2018-10-31 15:01:42 +02:00
Joona HoikkalaandGitHub 1d85bf75b0 Added Traefik and cert-manager to client list (#123) 2018-10-31 01:13:10 +02:00
Joona HoikkalaandGitHub c2c5c5cd70 Better error handling in goroutines (#122)
* More robust goroutine error handling using channels

* Fix tests and make startup log msg saner

* Clarification to README and config file
2018-10-31 00:54:51 +02:00
golint fixerandJoona Hoikkala a09073da12 Fix golint import path (#121) 2018-10-29 10:29:34 +02:00
Julien SchmidtandJoona Hoikkala 4bb5e265ab README: use absolute path for sqlite DB in example config (#116) 2018-09-28 11:40:50 +03:00
Julien SchmidtandJoona Hoikkala b452d504c8 README: fix NS address in test command (#115) 2018-09-28 11:14:31 +03:00
Yannik SembritzkiandJoona Hoikkala f76790426d fix inconsistency between README and actual config (#114)
* fix inconsistency between README and actual config

* clarify example config in sync w/ config.cfg
2018-09-28 00:46:07 +03:00
Yannik SembritzkiandJoona Hoikkala f650e47fe5 fix README inconsistencies (#113) 2018-09-28 00:33:51 +03:00
Yannik SembritzkiandJoona Hoikkala dc0dd43017 simplify example dns config (#106) 2018-09-27 23:58:37 +03:00
Julien SchmidtandJoona Hoikkala c827ee4801 README: fix section of HTTP header vars in example config (#112) 2018-09-27 23:51:31 +03:00
Stefan BethkeandJoona Hoikkala db2a6bc288 Cmd line flag -c for config location (#108)
Add command line parsing and a flag `-c` to specify where the config
file should be loaded from.
2018-09-21 13:38:23 +03:00
7 changed files with 99 additions and 57 deletions
+2 -1
View File
@@ -1,10 +1,11 @@
language: go language: go
go: go:
- 1.9 - 1.9
- 1.11
env: env:
- "PATH=/home/travis/gopath/bin:$PATH" - "PATH=/home/travis/gopath/bin:$PATH"
before_install: before_install:
- go get github.com/golang/lint/golint - go get golang.org/x/lint/golint
- go get github.com/mattn/goveralls - go get github.com/mattn/goveralls
script: script:
- go vet - go vet
+32 -25
View File
@@ -115,7 +115,7 @@ See the INSTALL section for information on how to do this.
2) Install acme-dns: `go get github.com/joohoi/acme-dns/...`. This will install acme-dns to `~/go/bin/acme-dns`. 2) Install acme-dns: `go get github.com/joohoi/acme-dns/...`. This will install acme-dns to `~/go/bin/acme-dns`.
3) Edit config.cfg to suit your needs (see [configuration](#configuration)). `acme-dns` will read the configuration file from `/etc/acme-dns/config.cfg` or `./config.cfg`. 3) Edit config.cfg to suit your needs (see [configuration](#configuration)). `acme-dns` will read the configuration file from `/etc/acme-dns/config.cfg` or `./config.cfg`, or a location specified with the `-c` flag.
4) If your system has systemd, you can optionally install acme-dns as a service so that it will start on boot and be tracked by systemd. This also allows us to add the `CAP_NET_BIND_SERVICE` capability so that acme-dns can be run by a user other than root. 4) If your system has systemd, you can optionally install acme-dns as a service so that it will start on boot and be tracked by systemd. This also allows us to add the `CAP_NET_BIND_SERVICE` capability so that acme-dns can be run by a user other than root.
@@ -168,15 +168,15 @@ docker run --rm --name acmedns \
## DNS Records ## DNS Records
Note: In this documentation: Note: In this documentation:
- `example.com` is your domain name - `auth.example.org` is the hostname of the acme-dns server
- `auth.example.com` is the subdomain you want to use for acme-dns - acme-dns will serve `*.auth.example.org` records
- `198.51.100.1` is the **public** IP address of the system running acme-dns - `198.51.100.1` is the **public** IP address of the system running acme-dns
These values should be changed based on your environment. These values should be changed based on your environment.
You will need to add some DNS records on your domain's regular DNS server: You will need to add some DNS records on your domain's regular DNS server:
- `NS` record for `auth.example.com` pointing to `ns.auth.example.com` - `NS` record for `auth.example.org` pointing to `auth.example.org` (this means, that `auth.example.org` is responsible for any `*.auth.example.org` records)
- `A` record for `ns.auth.example.com` pointing to `198.51.100.1` - `A` record for `auth.example.org` pointing to `198.51.100.1`
- If using IPv6, an `AAAA` record pointing to the IPv6 address. - If using IPv6, an `AAAA` record pointing to the IPv6 address.
- Each domain you will be authenticating will need a `_acme-challenge` `CNAME` subdomain added. The [client](README.md#clients) you use will explain how to do this. - Each domain you will be authenticating will need a `_acme-challenge` `CNAME` subdomain added. The [client](README.md#clients) you use will explain how to do this.
@@ -184,12 +184,12 @@ You will need to add some DNS records on your domain's regular DNS server:
You may want to test that acme-dns is working before using it for real queries. You may want to test that acme-dns is working before using it for real queries.
1) Confirm that DNS lookups for the acme-dns subdomain works as expected: `dig auth.example.com`. 1) Confirm that DNS lookups for the acme-dns subdomain works as expected: `dig auth.example.org`.
2) Call the `/register` API endpoint to register a test domain: 2) Call the `/register` API endpoint to register a test domain:
``` ```
$ curl -X POST http://auth.example.com/register $ curl -X POST http://auth.example.org/register
{"username":"eabcdb41-d89f-4580-826f-3e62e9755ef2","password":"pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0","fulldomain":"d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com","subdomain":"d420c923-bbd7-4056-ab64-c3ca54c9b3cf","allowfrom":[]} {"username":"eabcdb41-d89f-4580-826f-3e62e9755ef2","password":"pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0","fulldomain":"d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.org","subdomain":"d420c923-bbd7-4056-ab64-c3ca54c9b3cf","allowfrom":[]}
``` ```
3) Call the `/update` API endpoint to set a test TXT record. Pass the `username`, `password` and `subdomain` received from the `register` call performed above: 3) Call the `/update` API endpoint to set a test TXT record. Pass the `username`, `password` and `subdomain` received from the `register` call performed above:
@@ -198,40 +198,38 @@ $ curl -X POST \
-H "X-Api-User: eabcdb41-d89f-4580-826f-3e62e9755ef2" \ -H "X-Api-User: eabcdb41-d89f-4580-826f-3e62e9755ef2" \
-H "X-Api-Key: pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0" \ -H "X-Api-Key: pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0" \
-d '{"subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", "txt": "___validation_token_received_from_the_ca___"}' \ -d '{"subdomain": "d420c923-bbd7-4056-ab64-c3ca54c9b3cf", "txt": "___validation_token_received_from_the_ca___"}' \
http://auth.example.com/update http://auth.example.org/update
``` ```
Note: The `txt` field must be exactly 43 characters long, otherwise acme-dns will reject it Note: The `txt` field must be exactly 43 characters long, otherwise acme-dns will reject it
4) Perform a DNS lookup to the test subdomain to confirm that everything is working properly: 4) Perform a DNS lookup to the test subdomain to confirm that everything is working properly:
``` ```
$ dig @ns.auth.example.com d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com $ dig @auth.example.org d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.org
``` ```
## Configuration ## Configuration
```bash ```bash
[general] [general]
# dns interface # DNS interface. Note that systemd-resolved may reserve port 53 on 127.0.0.53
# In this case acme-dns will error out and you will need to define the listening interface
# for example: listen = "127.0.0.1:53"
listen = ":53" listen = ":53"
# protocol, "udp", "udp4", "udp6" or "tcp", "tcp4", "tcp6" # protocol, "udp", "udp4", "udp6" or "tcp", "tcp4", "tcp6"
protocol = "udp" protocol = "udp"
# domain name to serve the requests off of # domain name to serve the requests off of
domain = "auth.example.org" domain = "auth.example.org"
# zone name server # zone name server
nsname = "ns1.auth.example.org" nsname = "auth.example.org"
# admin email address, where @ is substituted with . # admin email address, where @ is substituted with .
nsadmin = "admin.example.org" nsadmin = "admin.example.org"
# predefined records served in addition to the TXT # predefined records served in addition to the TXT
records = [ records = [
# default A # domain pointing to the public IP of your acme-dns server
"auth.example.org. A 192.168.1.100", "auth.example.org. A 198.51.100.1",
# A # specify that auth.example.org will resolve any *.auth.example.org records
"ns1.auth.example.org. A 192.168.1.100", "auth.example.org. NS auth.example.org.",
"ns2.auth.example.org. A 192.168.1.100",
# NS
"auth.example.org. NS ns1.auth.example.org.",
"auth.example.org. NS ns2.auth.example.org.",
] ]
# debug messages from CORS etc # debug messages from CORS etc
debug = false debug = false
@@ -240,7 +238,7 @@ debug = false
# Database engine to use, sqlite3 or postgres # Database engine to use, sqlite3 or postgres
engine = "sqlite3" engine = "sqlite3"
# Connection string, filename for sqlite3 and postgres://$username:$password@$host/$db_name for postgres # Connection string, filename for sqlite3 and postgres://$username:$password@$host/$db_name for postgres
connection = "acme-dns.db" connection = "/var/lib/acme-dns/acme-dns.db"
# connection = "postgres://user:password@localhost/acmedns_db" # connection = "postgres://user:password@localhost/acmedns_db"
[api] [api]
@@ -265,6 +263,10 @@ acme_cache_dir = "api-certs"
corsorigins = [ corsorigins = [
"*" "*"
] ]
# use HTTP header to get the client ip
use_header = false
# header name to pull the ip address / list of ip addresses from
header_name = "X-Forwarded-For"
[logconfig] [logconfig]
# logging level: "error", "warning", "info" or "debug" # logging level: "error", "warning", "info" or "debug"
@@ -275,18 +277,16 @@ logtype = "stdout"
# logfile = "./acme-dns.log" # logfile = "./acme-dns.log"
# format, either "json" or "text" # format, either "json" or "text"
logformat = "text" logformat = "text"
# use HTTP header to get the client ip
use_header = false
# header name to pull the ip address / list of ip addresses from
header_name = "X-Forwarded-For"
``` ```
## Clients ## Clients
- acme.sh: [https://github.com/Neilpang/acme.sh](https://github.com/Neilpang/acme.sh) - acme.sh: [https://github.com/Neilpang/acme.sh](https://github.com/Neilpang/acme.sh)
- cert-manager: [https://github.com/jetstack/cert-manager](https://github.com/jetstack/cert-manager)
- Lego: [https://github.com/xenolf/lego](https://github.com/xenolf/lego) - Lego: [https://github.com/xenolf/lego](https://github.com/xenolf/lego)
- Posh-ACME: [https://github.com/rmbolger/Posh-ACME](https://github.com/rmbolger/Posh-ACME) - Posh-ACME: [https://github.com/rmbolger/Posh-ACME](https://github.com/rmbolger/Posh-ACME)
- Sewer: [https://github.com/komuw/sewer](https://github.com/komuw/sewer) - Sewer: [https://github.com/komuw/sewer](https://github.com/komuw/sewer)
- Traefik: [https://github.com/containous/traefik](https://github.com/containous/traefik)
### Authentication hooks ### Authentication hooks
@@ -300,6 +300,13 @@ header_name = "X-Forwarded-For"
## Changelog ## Changelog
- 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 - v0.5
- New - New
- Configurable certificate cache directory - Configurable certificate cache directory
+8 -10
View File
@@ -1,24 +1,22 @@
[general] [general]
# dns interface # DNS interface. Note that systemd-resolved may reserve port 53 on 127.0.0.53
# In this case acme-dns will error out and you will need to define the listening interface
# for example: listen = "127.0.0.1:53"
listen = ":53" listen = ":53"
# protocol, "udp", "udp4", "udp6" or "tcp", "tcp4", "tcp6" # protocol, "udp", "udp4", "udp6" or "tcp", "tcp4", "tcp6"
protocol = "udp" protocol = "udp"
# domain name to serve the requests off of # domain name to serve the requests off of
domain = "auth.example.org" domain = "auth.example.org"
# zone name server # zone name server
nsname = "ns1.auth.example.org" nsname = "auth.example.org"
# admin email address, where @ is substituted with . # admin email address, where @ is substituted with .
nsadmin = "admin.example.org" nsadmin = "admin.example.org"
# predefined records served in addition to the TXT # predefined records served in addition to the TXT
records = [ records = [
# default A # domain pointing to the public IP of your acme-dns server
"auth.example.org. A 192.168.1.100", "auth.example.org. A 198.51.100.1",
# A # specify that auth.example.org will resolve any *.auth.example.org records
"ns1.auth.example.org. A 192.168.1.100", "auth.example.org. NS auth.example.org.",
"ns2.auth.example.org. A 192.168.1.100",
# NS
"auth.example.org. NS ns1.auth.example.org.",
"auth.example.org. NS ns2.auth.example.org.",
] ]
# debug messages from CORS etc # debug messages from CORS etc
debug = false debug = false
+3 -1
View File
@@ -56,7 +56,6 @@ func answer(q dns.Question) ([]dns.RR, int, error) {
if !ok { if !ok {
rcode = dns.RcodeNameError rcode = dns.RcodeNameError
} }
} }
log.WithFields(log.Fields{"qtype": dns.TypeToString[rtype], "domain": domain, "rcode": dns.RcodeToString[rcode]}).Debug("Answering question for domain") log.WithFields(log.Fields{"qtype": dns.TypeToString[rtype], "domain": domain, "rcode": dns.RcodeToString[rcode]}).Debug("Answering question for domain")
return r, rcode, nil return r, rcode, nil
@@ -68,6 +67,9 @@ func handleRequest(w dns.ResponseWriter, r *dns.Msg) {
if r.Opcode == dns.OpcodeQuery { if r.Opcode == dns.OpcodeQuery {
readQuery(m) readQuery(m)
} else if r.Opcode == dns.OpcodeUpdate {
log.Debug("Refusing DNS Dynamic update request")
m.MsgHdr.Rcode = dns.RcodeRefused
} }
w.WriteMsg(m) w.WriteMsg(m)
+42 -10
View File
@@ -4,12 +4,14 @@ package main
import ( import (
"crypto/tls" "crypto/tls"
"flag"
stdlog "log" stdlog "log"
"net/http" "net/http"
"os" "os"
"syscall" "syscall"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/miekg/dns"
"github.com/rs/cors" "github.com/rs/cors"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/crypto/acme/autocert" "golang.org/x/crypto/acme/autocert"
@@ -18,11 +20,13 @@ import (
func main() { func main() {
// Created files are not world writable // Created files are not world writable
syscall.Umask(0077) syscall.Umask(0077)
configPtr := flag.String("c", "/etc/acme-dns/config.cfg", "config file location")
flag.Parse()
// Read global config // Read global config
var err error var err error
if fileIsAccessible("/etc/acme-dns/config.cfg") { if fileIsAccessible(*configPtr) {
log.WithFields(log.Fields{"file": "/etc/acme-dns/config.cfg"}).Info("Using config file") log.WithFields(log.Fields{"file": *configPtr}).Info("Using config file")
Config, err = readConfig("/etc/acme-dns/config.cfg") Config, err = readConfig(*configPtr)
} else if fileIsAccessible("./config.cfg") { } else if fileIsAccessible("./config.cfg") {
log.WithFields(log.Fields{"file": "./config.cfg"}).Info("Using config file") log.WithFields(log.Fields{"file": "./config.cfg"}).Info("Using config file")
Config, err = readConfig("./config.cfg") Config, err = readConfig("./config.cfg")
@@ -52,16 +56,41 @@ func main() {
DB = newDB DB = newDB
defer DB.Close() defer DB.Close()
// Error channel for servers
errChan := make(chan error, 1)
// DNS server // DNS server
startDNS(Config.General.Listen, Config.General.Proto) dnsServer := setupDNSServer()
go startDNS(dnsServer, errChan)
// HTTP API // HTTP API
startHTTPAPI() go startHTTPAPI(errChan)
// block waiting for error
select {
case err = <-errChan:
if err != nil {
log.Fatal(err)
}
}
log.Debugf("Shutting down...") log.Debugf("Shutting down...")
} }
func startHTTPAPI() { func startDNS(server *dns.Server, errChan chan error) {
// DNS server part
dns.HandleFunc(".", handleRequest)
log.WithFields(log.Fields{"addr": Config.General.Listen}).Info("Listening DNS")
err := server.ListenAndServe()
if err != nil {
errChan <- err
}
}
func setupDNSServer() *dns.Server {
return &dns.Server{Addr: Config.General.Listen, Net: Config.General.Proto}
}
func startHTTPAPI(errChan chan error) {
// Setup http logger // Setup http logger
logger := log.New() logger := log.New()
logwriter := logger.Writer() logwriter := logger.Writer()
@@ -87,7 +116,7 @@ func startHTTPAPI() {
cfg := &tls.Config{ cfg := &tls.Config{
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
} }
var err error
switch Config.API.TLS { switch Config.API.TLS {
case "letsencrypt": case "letsencrypt":
m := autocert.Manager{ m := autocert.Manager{
@@ -106,7 +135,7 @@ func startHTTPAPI() {
ErrorLog: stdlog.New(logwriter, "", 0), ErrorLog: stdlog.New(logwriter, "", 0),
} }
log.WithFields(log.Fields{"host": host, "domain": Config.API.Domain}).Info("Listening HTTPS, using certificate from autocert") log.WithFields(log.Fields{"host": host, "domain": Config.API.Domain}).Info("Listening HTTPS, using certificate from autocert")
log.Fatal(srv.ListenAndServeTLS("", "")) err = srv.ListenAndServeTLS("", "")
case "cert": case "cert":
srv := &http.Server{ srv := &http.Server{
Addr: host, Addr: host,
@@ -115,9 +144,12 @@ func startHTTPAPI() {
ErrorLog: stdlog.New(logwriter, "", 0), ErrorLog: stdlog.New(logwriter, "", 0),
} }
log.WithFields(log.Fields{"host": host}).Info("Listening HTTPS") log.WithFields(log.Fields{"host": host}).Info("Listening HTTPS")
log.Fatal(srv.ListenAndServeTLS(Config.API.TLSCertFullchain, Config.API.TLSCertPrivkey)) err = srv.ListenAndServeTLS(Config.API.TLSCertFullchain, Config.API.TLSCertPrivkey)
default: default:
log.WithFields(log.Fields{"host": host}).Info("Listening HTTP") log.WithFields(log.Fields{"host": host}).Info("Listening HTTP")
log.Fatal(http.ListenAndServe(host, c.Handler(api))) err = http.ListenAndServe(host, c.Handler(api))
}
if err != nil {
errChan <- err
} }
} }
+12 -1
View File
@@ -7,6 +7,7 @@ import (
logrustest "github.com/sirupsen/logrus/hooks/test" logrustest "github.com/sirupsen/logrus/hooks/test"
"io/ioutil" "io/ioutil"
"os" "os"
"sync"
"testing" "testing"
) )
@@ -42,7 +43,15 @@ func TestMain(m *testing.M) {
_ = newDb.Init("sqlite3", ":memory:") _ = newDb.Init("sqlite3", ":memory:")
} }
DB = newDb DB = newDb
server := startDNS("0.0.0.0:15353", "udp") server := setupDNSServer()
// Make sure that we're not creating a race condition in tests
var wg sync.WaitGroup
wg.Add(1)
server.NotifyStartedFunc = func() {
wg.Done()
}
go startDNS(server, make(chan error, 1))
wg.Wait()
exitval := m.Run() exitval := m.Run()
server.Shutdown() server.Shutdown()
DB.Close() DB.Close()
@@ -57,6 +66,8 @@ func setupConfig() {
var generalcfg = general{ var generalcfg = general{
Domain: "auth.example.org", Domain: "auth.example.org",
Listen: "127.0.0.1:15353",
Proto: "udp",
Nsname: "ns1.auth.example.org", Nsname: "ns1.auth.example.org",
Nsadmin: "admin.example.org", Nsadmin: "admin.example.org",
StaticRecords: records, StaticRecords: records,
-9
View File
@@ -10,7 +10,6 @@ import (
"strings" "strings"
"github.com/BurntSushi/toml" "github.com/BurntSushi/toml"
"github.com/miekg/dns"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@@ -108,14 +107,6 @@ func setupLogging(format string, level string) {
// TODO: file logging // TODO: file logging
} }
func startDNS(listen string, proto string) *dns.Server {
// DNS server part
dns.HandleFunc(".", handleRequest)
server := &dns.Server{Addr: listen, Net: proto}
go server.ListenAndServe()
return server
}
func getIPListFromHeader(header string) []string { func getIPListFromHeader(header string) []string {
iplist := []string{} iplist := []string{}
for _, v := range strings.Split(header, ",") { for _, v := range strings.Split(header, ",") {