From fe7c20706563d62c513ed72992d049f6cd7daf87 Mon Sep 17 00:00:00 2001 From: Niklas Date: Fri, 8 Jan 2021 16:08:57 +0100 Subject: [PATCH] implemented lego HTTP endpoint --- config.yml | 6 +++ coolDns.go | 20 ++++++++- lego.go | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 lego.go diff --git a/config.yml b/config.yml index 8ffa469..e0fc0ad 100644 --- a/config.yml +++ b/config.yml @@ -33,3 +33,9 @@ blacklist: format: host - url: https://blocklistproject.github.io/Lists/alt-version/ads-nl.txt format: line + +lego: + enable: true + address: :8080 + username: lego + secret: "133742069ab" \ No newline at end of file diff --git a/coolDns.go b/coolDns.go index 9914591..9538e66 100644 --- a/coolDns.go +++ b/coolDns.go @@ -31,6 +31,7 @@ type config struct { Address string `yaml:"address"` Blacklist []configBlacklist `yaml:"blacklist"` TLS configTLS `yaml:"tls"` + Lego configLego `yaml:"lego"` } type configForward struct { @@ -168,7 +169,7 @@ func createACLList(config []configACL) (map[string]*net.IPNet, error) { } // createServer creates a new serve mux. Adds all the logic to handle the request -func createServer(zones zoneMap, config config, aclList map[string]*net.IPNet, blacklist map[string]bool) *dns.ServeMux { +func createServer(zones zoneMap, config config, aclList map[string]*net.IPNet, blacklist map[string]bool, acmeList *legoMap) *dns.ServeMux { srv := dns.NewServeMux() c := new(dns.Client) @@ -184,6 +185,11 @@ func createServer(zones zoneMap, config config, aclList map[string]*net.IPNet, b return } + // Check if it is a ACME DNS-01 challange + if handleACMERequest(w, r, acmeList) { + return + } + // find out what view to handle the request zoneIndex := -1 @@ -215,6 +221,11 @@ func createServer(zones zoneMap, config config, aclList map[string]*net.IPNet, b return } + // Check if it is a ACME DNS-01 challange + if handleACMERequest(w, r, acmeList) { + return + } + // Check ACL rules if !checkACL(config.Forward.ACL, aclList, ip) { rcodeRequest(w, r, dns.RcodeRefused) @@ -389,7 +400,12 @@ func main() { blacklist := loadBlacklist(config.Blacklist) - server := createServer(zones, *config, aclList, blacklist) + var acmeMap *legoMap + if config.Lego.Enable { + acmeMap = startLEGOWebSever(config.Lego) + } + + server := createServer(zones, *config, aclList, blacklist, acmeMap) listenAndServer(server, config.Address) diff --git a/lego.go b/lego.go new file mode 100644 index 0000000..aa0525e --- /dev/null +++ b/lego.go @@ -0,0 +1,118 @@ +package main + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/miekg/dns" +) + +// See https://go-acme.github.io/lego/dns/httpreq/ + +type configLego struct { + Enable bool `yaml:"enable"` + Address string `yaml:"address"` + Username string `yaml:"username"` + Secret string `yaml:"secret"` +} + +type legoPresent struct { + Fqdn string `json:"fqdn"` + Value string `json:"value"` +} + +type legoMap map[string]string + +func startLEGOWebSever(config configLego) *legoMap { + mux := http.NewServeMux() + + acmeMap := make(legoMap) + + mux.HandleFunc("/present", func(rw http.ResponseWriter, r *http.Request) { + + if !checkBasicAuth(r, config) { + rw.WriteHeader(http.StatusUnauthorized) + return + } + + var presentData legoPresent + err := json.NewDecoder(r.Body).Decode(&presentData) + defer r.Body.Close() + + if err != nil { + log.Printf("Failed to parse request for ACME: %s", err.Error()) + } + + acmeMap[presentData.Fqdn] = presentData.Value + + rw.WriteHeader(http.StatusOK) + }) + + mux.HandleFunc("/cleanup", func(rw http.ResponseWriter, r *http.Request) { + if !checkBasicAuth(r, config) { + rw.WriteHeader(http.StatusUnauthorized) + return + } + + for k := range acmeMap { + delete(acmeMap, k) + } + + rw.WriteHeader(http.StatusOK) + }) + + go func() { + if err := http.ListenAndServe(config.Address, mux); err != nil { + log.Fatalf("Failed to start Webserver for LEGO: %s\n", err.Error()) + } + }() + + log.Printf("Startet webserver on %s", config.Address) + + return &acmeMap +} + +func checkBasicAuth(r *http.Request, config configLego) bool { + if config.Username != "" && config.Secret != "" { + u, p, ok := r.BasicAuth() + if !ok { + return false + } + + if u == config.Username && p == config.Secret { + return true + } + + log.Printf("Failed lego authentication") + + return false + } + return true +} + +func handleACMERequest(w dns.ResponseWriter, r *dns.Msg, acmeMap *legoMap) bool { + if len(r.Question) == 1 { + if r.Question[0].Qtype == dns.TypeTXT && r.Question[0].Qclass == dns.ClassINET { + if value, ok := (*acmeMap)[r.Question[0].Name]; ok { + m := new(dns.Msg) + m.SetReply(r) + + m.Answer = append(m.Answer, &dns.TXT{ + Hdr: dns.RR_Header{ + Name: r.Question[0].Name, + Rrtype: dns.TypeTXT, + Class: dns.ClassINET, + Ttl: 0, + }, + Txt: []string{value}, + }) + + w.WriteMsg(m) + return true + } + } + } + + return false +}