Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20411b650f | ||
|
|
3bb130d055 | ||
|
|
1d85bf75b0 | ||
|
|
c2c5c5cd70 | ||
|
|
a09073da12 | ||
|
|
4bb5e265ab | ||
|
|
b452d504c8 | ||
|
|
f76790426d | ||
|
|
f650e47fe5 | ||
|
|
dc0dd43017 | ||
|
|
c827ee4801 | ||
|
|
db2a6bc288 |
+2
-1
@@ -1,10 +1,11 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.9
|
||||
- 1.11
|
||||
env:
|
||||
- "PATH=/home/travis/gopath/bin:$PATH"
|
||||
before_install:
|
||||
- go get github.com/golang/lint/golint
|
||||
- go get golang.org/x/lint/golint
|
||||
- go get github.com/mattn/goveralls
|
||||
script:
|
||||
- go vet
|
||||
|
||||
@@ -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`.
|
||||
|
||||
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.
|
||||
|
||||
@@ -168,15 +168,15 @@ docker run --rm --name acmedns \
|
||||
## DNS Records
|
||||
|
||||
Note: In this documentation:
|
||||
- `example.com` is your domain name
|
||||
- `auth.example.com` is the subdomain you want to use for acme-dns
|
||||
- `auth.example.org` is the hostname of the acme-dns server
|
||||
- acme-dns will serve `*.auth.example.org` records
|
||||
- `198.51.100.1` is the **public** IP address of the system running acme-dns
|
||||
|
||||
These values should be changed based on your environment.
|
||||
|
||||
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`
|
||||
- `A` record for `ns.auth.example.com` pointing to `198.51.100.1`
|
||||
- `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 `auth.example.org` pointing to `198.51.100.1`
|
||||
- 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
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:
|
||||
```
|
||||
$ curl -X POST http://auth.example.com/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":[]}
|
||||
$ curl -X POST http://auth.example.org/register
|
||||
{"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:
|
||||
@@ -198,40 +198,38 @@ $ curl -X POST \
|
||||
-H "X-Api-User: eabcdb41-d89f-4580-826f-3e62e9755ef2" \
|
||||
-H "X-Api-Key: pbAXVjlIOE01xbut7YnAbkhMQIkcwoHO0ek2j4Q0" \
|
||||
-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
|
||||
|
||||
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
|
||||
|
||||
```bash
|
||||
[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"
|
||||
# protocol, "udp", "udp4", "udp6" or "tcp", "tcp4", "tcp6"
|
||||
protocol = "udp"
|
||||
# domain name to serve the requests off of
|
||||
domain = "auth.example.org"
|
||||
# zone name server
|
||||
nsname = "ns1.auth.example.org"
|
||||
nsname = "auth.example.org"
|
||||
# admin email address, where @ is substituted with .
|
||||
nsadmin = "admin.example.org"
|
||||
# predefined records served in addition to the TXT
|
||||
records = [
|
||||
# default A
|
||||
"auth.example.org. A 192.168.1.100",
|
||||
# A
|
||||
"ns1.auth.example.org. A 192.168.1.100",
|
||||
"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.",
|
||||
# domain pointing to the public IP of your acme-dns server
|
||||
"auth.example.org. A 198.51.100.1",
|
||||
# specify that auth.example.org will resolve any *.auth.example.org records
|
||||
"auth.example.org. NS auth.example.org.",
|
||||
]
|
||||
# debug messages from CORS etc
|
||||
debug = false
|
||||
@@ -240,7 +238,7 @@ debug = false
|
||||
# Database engine to use, sqlite3 or postgres
|
||||
engine = "sqlite3"
|
||||
# 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"
|
||||
|
||||
[api]
|
||||
@@ -265,6 +263,10 @@ acme_cache_dir = "api-certs"
|
||||
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]
|
||||
# logging level: "error", "warning", "info" or "debug"
|
||||
@@ -275,18 +277,16 @@ logtype = "stdout"
|
||||
# logfile = "./acme-dns.log"
|
||||
# format, either "json" or "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
|
||||
|
||||
- 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)
|
||||
- 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)
|
||||
- Traefik: [https://github.com/containous/traefik](https://github.com/containous/traefik)
|
||||
|
||||
### Authentication hooks
|
||||
|
||||
@@ -300,6 +300,13 @@ header_name = "X-Forwarded-For"
|
||||
|
||||
|
||||
## 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
|
||||
- New
|
||||
- Configurable certificate cache directory
|
||||
|
||||
+8
-10
@@ -1,24 +1,22 @@
|
||||
[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"
|
||||
# protocol, "udp", "udp4", "udp6" or "tcp", "tcp4", "tcp6"
|
||||
protocol = "udp"
|
||||
# domain name to serve the requests off of
|
||||
domain = "auth.example.org"
|
||||
# zone name server
|
||||
nsname = "ns1.auth.example.org"
|
||||
nsname = "auth.example.org"
|
||||
# admin email address, where @ is substituted with .
|
||||
nsadmin = "admin.example.org"
|
||||
# predefined records served in addition to the TXT
|
||||
records = [
|
||||
# default A
|
||||
"auth.example.org. A 192.168.1.100",
|
||||
# A
|
||||
"ns1.auth.example.org. A 192.168.1.100",
|
||||
"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.",
|
||||
# domain pointing to the public IP of your acme-dns server
|
||||
"auth.example.org. A 198.51.100.1",
|
||||
# specify that auth.example.org will resolve any *.auth.example.org records
|
||||
"auth.example.org. NS auth.example.org.",
|
||||
]
|
||||
# debug messages from CORS etc
|
||||
debug = false
|
||||
|
||||
@@ -56,7 +56,6 @@ func answer(q dns.Question) ([]dns.RR, int, error) {
|
||||
if !ok {
|
||||
rcode = dns.RcodeNameError
|
||||
}
|
||||
|
||||
}
|
||||
log.WithFields(log.Fields{"qtype": dns.TypeToString[rtype], "domain": domain, "rcode": dns.RcodeToString[rcode]}).Debug("Answering question for domain")
|
||||
return r, rcode, nil
|
||||
@@ -68,6 +67,9 @@ func handleRequest(w dns.ResponseWriter, r *dns.Msg) {
|
||||
|
||||
if r.Opcode == dns.OpcodeQuery {
|
||||
readQuery(m)
|
||||
} else if r.Opcode == dns.OpcodeUpdate {
|
||||
log.Debug("Refusing DNS Dynamic update request")
|
||||
m.MsgHdr.Rcode = dns.RcodeRefused
|
||||
}
|
||||
|
||||
w.WriteMsg(m)
|
||||
|
||||
@@ -4,12 +4,14 @@ package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
stdlog "log"
|
||||
"net/http"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/rs/cors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
@@ -18,11 +20,13 @@ import (
|
||||
func main() {
|
||||
// Created files are not world writable
|
||||
syscall.Umask(0077)
|
||||
configPtr := flag.String("c", "/etc/acme-dns/config.cfg", "config file location")
|
||||
flag.Parse()
|
||||
// Read global config
|
||||
var err error
|
||||
if fileIsAccessible("/etc/acme-dns/config.cfg") {
|
||||
log.WithFields(log.Fields{"file": "/etc/acme-dns/config.cfg"}).Info("Using config file")
|
||||
Config, err = readConfig("/etc/acme-dns/config.cfg")
|
||||
if fileIsAccessible(*configPtr) {
|
||||
log.WithFields(log.Fields{"file": *configPtr}).Info("Using config file")
|
||||
Config, err = readConfig(*configPtr)
|
||||
} else if fileIsAccessible("./config.cfg") {
|
||||
log.WithFields(log.Fields{"file": "./config.cfg"}).Info("Using config file")
|
||||
Config, err = readConfig("./config.cfg")
|
||||
@@ -52,16 +56,41 @@ func main() {
|
||||
DB = newDB
|
||||
defer DB.Close()
|
||||
|
||||
// Error channel for servers
|
||||
errChan := make(chan error, 1)
|
||||
|
||||
// DNS server
|
||||
startDNS(Config.General.Listen, Config.General.Proto)
|
||||
dnsServer := setupDNSServer()
|
||||
go startDNS(dnsServer, errChan)
|
||||
|
||||
// HTTP API
|
||||
startHTTPAPI()
|
||||
go startHTTPAPI(errChan)
|
||||
|
||||
// block waiting for error
|
||||
select {
|
||||
case err = <-errChan:
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
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
|
||||
logger := log.New()
|
||||
logwriter := logger.Writer()
|
||||
@@ -87,7 +116,7 @@ func startHTTPAPI() {
|
||||
cfg := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
|
||||
var err error
|
||||
switch Config.API.TLS {
|
||||
case "letsencrypt":
|
||||
m := autocert.Manager{
|
||||
@@ -106,7 +135,7 @@ func startHTTPAPI() {
|
||||
ErrorLog: stdlog.New(logwriter, "", 0),
|
||||
}
|
||||
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":
|
||||
srv := &http.Server{
|
||||
Addr: host,
|
||||
@@ -115,9 +144,12 @@ func startHTTPAPI() {
|
||||
ErrorLog: stdlog.New(logwriter, "", 0),
|
||||
}
|
||||
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:
|
||||
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
@@ -7,6 +7,7 @@ import (
|
||||
logrustest "github.com/sirupsen/logrus/hooks/test"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -42,7 +43,15 @@ func TestMain(m *testing.M) {
|
||||
_ = newDb.Init("sqlite3", ":memory:")
|
||||
}
|
||||
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()
|
||||
server.Shutdown()
|
||||
DB.Close()
|
||||
@@ -57,6 +66,8 @@ func setupConfig() {
|
||||
|
||||
var generalcfg = general{
|
||||
Domain: "auth.example.org",
|
||||
Listen: "127.0.0.1:15353",
|
||||
Proto: "udp",
|
||||
Nsname: "ns1.auth.example.org",
|
||||
Nsadmin: "admin.example.org",
|
||||
StaticRecords: records,
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/miekg/dns"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -108,14 +107,6 @@ func setupLogging(format string, level string) {
|
||||
// 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 {
|
||||
iplist := []string{}
|
||||
for _, v := range strings.Split(header, ",") {
|
||||
|
||||
Reference in New Issue
Block a user