From 0c71172eb80dfc68de30b2901ce20be9f3e7ca1f Mon Sep 17 00:00:00 2001 From: novikov Date: Mon, 4 Apr 2022 12:59:56 +0700 Subject: [PATCH] Add localserver DNS provider --- README.md | 24 ++-- cmd/zz_gen_cmd_dnshelp.go | 15 +++ docs/content/dns/zz_gen_local.md | 47 ++++++++ providers/dns/dns_providers.go | 3 + providers/dns/localserver/localserver.go | 111 ++++++++++++++++++ providers/dns/localserver/localserver.toml | 13 ++ providers/dns/localserver/localserver_test.go | 45 +++++++ 7 files changed, 246 insertions(+), 12 deletions(-) create mode 100644 docs/content/dns/zz_gen_local.md create mode 100644 providers/dns/localserver/localserver.go create mode 100644 providers/dns/localserver/localserver.toml create mode 100644 providers/dns/localserver/localserver_test.go diff --git a/README.md b/README.md index e58071620a..7a113825a8 100644 --- a/README.md +++ b/README.md @@ -60,18 +60,18 @@ Detailed documentation is available [here](https://go-acme.github.io/lego/dns). | [HTTP request](https://go-acme.github.io/lego/dns/httpreq/) | [Hurricane Electric DNS](https://go-acme.github.io/lego/dns/hurricane/) | [HyperOne](https://go-acme.github.io/lego/dns/hyperone/) | [IBM Cloud (SoftLayer)](https://go-acme.github.io/lego/dns/ibmcloud/) | | [Infoblox](https://go-acme.github.io/lego/dns/infoblox/) | [Infomaniak](https://go-acme.github.io/lego/dns/infomaniak/) | [Internet Initiative Japan](https://go-acme.github.io/lego/dns/iij/) | [Internet.bs](https://go-acme.github.io/lego/dns/internetbs/) | | [INWX](https://go-acme.github.io/lego/dns/inwx/) | [Ionos](https://go-acme.github.io/lego/dns/ionos/) | [iwantmyname](https://go-acme.github.io/lego/dns/iwantmyname/) | [Joker](https://go-acme.github.io/lego/dns/joker/) | -| [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns/) | [Linode (v4)](https://go-acme.github.io/lego/dns/linode/) | [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/) | [Loopia](https://go-acme.github.io/lego/dns/loopia/) | -| [LuaDNS](https://go-acme.github.io/lego/dns/luadns/) | [Manual](https://go-acme.github.io/lego/dns/manual/) | [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) | [MythicBeasts](https://go-acme.github.io/lego/dns/mythicbeasts/) | -| [Name.com](https://go-acme.github.io/lego/dns/namedotcom/) | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/) | [Namesilo](https://go-acme.github.io/lego/dns/namesilo/) | [Netcup](https://go-acme.github.io/lego/dns/netcup/) | -| [Netlify](https://go-acme.github.io/lego/dns/netlify/) | [Nicmanager](https://go-acme.github.io/lego/dns/nicmanager/) | [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) | [Njalla](https://go-acme.github.io/lego/dns/njalla/) | -| [NS1](https://go-acme.github.io/lego/dns/ns1/) | [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/) | [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/) | [OVH](https://go-acme.github.io/lego/dns/ovh/) | -| [Porkbun](https://go-acme.github.io/lego/dns/porkbun/) | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/) | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/) | [reg.ru](https://go-acme.github.io/lego/dns/regru/) | -| [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/) | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) | [Scaleway](https://go-acme.github.io/lego/dns/scaleway/) | -| [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Servercow](https://go-acme.github.io/lego/dns/servercow/) | [Simply.com](https://go-acme.github.io/lego/dns/simply/) | [Sonic](https://go-acme.github.io/lego/dns/sonic/) | -| [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) | [Tencent Cloud DNS](https://go-acme.github.io/lego/dns/tencentcloud/) | [TransIP](https://go-acme.github.io/lego/dns/transip/) | [UKFast SafeDNS](https://go-acme.github.io/lego/dns/safedns/) | -| [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/) | [Vscale](https://go-acme.github.io/lego/dns/vscale/) | -| [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [WEDOS](https://go-acme.github.io/lego/dns/wedos/) | [Yandex](https://go-acme.github.io/lego/dns/yandex/) | [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | -| [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | | | | +| [Joohoi's ACME-DNS](https://go-acme.github.io/lego/dns/acme-dns/) | [Linode (v4)](https://go-acme.github.io/lego/dns/linode/) | [Liquid Web](https://go-acme.github.io/lego/dns/liquidweb/) | [Local](https://go-acme.github.io/lego/dns/local/) | +| [Loopia](https://go-acme.github.io/lego/dns/loopia/) | [LuaDNS](https://go-acme.github.io/lego/dns/luadns/) | [Manual](https://go-acme.github.io/lego/dns/manual/) | [MyDNS.jp](https://go-acme.github.io/lego/dns/mydnsjp/) | +| [MythicBeasts](https://go-acme.github.io/lego/dns/mythicbeasts/) | [Name.com](https://go-acme.github.io/lego/dns/namedotcom/) | [Namecheap](https://go-acme.github.io/lego/dns/namecheap/) | [Namesilo](https://go-acme.github.io/lego/dns/namesilo/) | +| [Netcup](https://go-acme.github.io/lego/dns/netcup/) | [Netlify](https://go-acme.github.io/lego/dns/netlify/) | [Nicmanager](https://go-acme.github.io/lego/dns/nicmanager/) | [NIFCloud](https://go-acme.github.io/lego/dns/nifcloud/) | +| [Njalla](https://go-acme.github.io/lego/dns/njalla/) | [NS1](https://go-acme.github.io/lego/dns/ns1/) | [Open Telekom Cloud](https://go-acme.github.io/lego/dns/otc/) | [Oracle Cloud](https://go-acme.github.io/lego/dns/oraclecloud/) | +| [OVH](https://go-acme.github.io/lego/dns/ovh/) | [Porkbun](https://go-acme.github.io/lego/dns/porkbun/) | [PowerDNS](https://go-acme.github.io/lego/dns/pdns/) | [Rackspace](https://go-acme.github.io/lego/dns/rackspace/) | +| [reg.ru](https://go-acme.github.io/lego/dns/regru/) | [RFC2136](https://go-acme.github.io/lego/dns/rfc2136/) | [RimuHosting](https://go-acme.github.io/lego/dns/rimuhosting/) | [Sakura Cloud](https://go-acme.github.io/lego/dns/sakuracloud/) | +| [Scaleway](https://go-acme.github.io/lego/dns/scaleway/) | [Selectel](https://go-acme.github.io/lego/dns/selectel/) | [Servercow](https://go-acme.github.io/lego/dns/servercow/) | [Simply.com](https://go-acme.github.io/lego/dns/simply/) | +| [Sonic](https://go-acme.github.io/lego/dns/sonic/) | [Stackpath](https://go-acme.github.io/lego/dns/stackpath/) | [Tencent Cloud DNS](https://go-acme.github.io/lego/dns/tencentcloud/) | [TransIP](https://go-acme.github.io/lego/dns/transip/) | +| [UKFast SafeDNS](https://go-acme.github.io/lego/dns/safedns/) | [VegaDNS](https://go-acme.github.io/lego/dns/vegadns/) | [Versio.[nl/eu/uk]](https://go-acme.github.io/lego/dns/versio/) | [VinylDNS](https://go-acme.github.io/lego/dns/vinyldns/) | +| [Vscale](https://go-acme.github.io/lego/dns/vscale/) | [Vultr](https://go-acme.github.io/lego/dns/vultr/) | [WEDOS](https://go-acme.github.io/lego/dns/wedos/) | [Yandex](https://go-acme.github.io/lego/dns/yandex/) | +| [Zone.ee](https://go-acme.github.io/lego/dns/zoneee/) | [Zonomi](https://go-acme.github.io/lego/dns/zonomi/) | | | diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go index 573d6358e1..8473ee57fb 100644 --- a/cmd/zz_gen_cmd_dnshelp.go +++ b/cmd/zz_gen_cmd_dnshelp.go @@ -72,6 +72,7 @@ func allDNSCodes() string { "lightsail", "linode", "liquidweb", + "local", "loopia", "luadns", "mydnsjp", @@ -1351,6 +1352,20 @@ func displayDNSHelp(name string) error { ew.writeln() ew.writeln(`More information: https://go-acme.github.io/lego/dns/liquidweb`) + case "local": + // generated from: providers/dns/local/local.toml + ew.writeln(`Configuration for Local.`) + ew.writeln(`Code: 'local'`) + ew.writeln(`Since: 'v0.0.1'`) + ew.writeln() + + ew.writeln(`Credentials:`) + ew.writeln(` - "LOCAL_LISTEN": Listen udp dns-server address`) + ew.writeln() + + ew.writeln() + ew.writeln(`More information: https://go-acme.github.io/lego/dns/local`) + case "loopia": // generated from: providers/dns/loopia/loopia.toml ew.writeln(`Configuration for Loopia.`) diff --git a/docs/content/dns/zz_gen_local.md b/docs/content/dns/zz_gen_local.md new file mode 100644 index 0000000000..eb44454094 --- /dev/null +++ b/docs/content/dns/zz_gen_local.md @@ -0,0 +1,47 @@ +--- +title: "Local" +date: 2019-03-03T16:39:46+01:00 +draft: false +slug: local +--- + + + + + +Since: v0.0.1 +Setup local udp dns server that can serve forawrded requests from main dns server + + + + +- Code: `local` + +Here is an example bash command using the Local provider: + +```bash +LOCAL_LISTEN=:5353 \ +lego --email myemail@example.com --dns local --domains my.example.org run +``` + + + + +## Credentials + +| Environment Variable Name | Description | +|-----------------------|-------------| +| `LOCAL_LISTEN` | Listen udp dns-server address | + +The environment variable names can be suffixed by `_FILE` to reference a file instead of a value. +More information [here](/lego/dns/#configuration-and-credentials). + + + + + + + + + + diff --git a/providers/dns/dns_providers.go b/providers/dns/dns_providers.go index 7c2af78d64..25ecf24a75 100644 --- a/providers/dns/dns_providers.go +++ b/providers/dns/dns_providers.go @@ -63,6 +63,7 @@ import ( "github.com/go-acme/lego/v4/providers/dns/lightsail" "github.com/go-acme/lego/v4/providers/dns/linode" "github.com/go-acme/lego/v4/providers/dns/liquidweb" + "github.com/go-acme/lego/v4/providers/dns/localserver" "github.com/go-acme/lego/v4/providers/dns/loopia" "github.com/go-acme/lego/v4/providers/dns/luadns" "github.com/go-acme/lego/v4/providers/dns/mydnsjp" @@ -312,6 +313,8 @@ func NewDNSChallengeProviderByName(name string) (challenge.Provider, error) { return zoneee.NewDNSProvider() case "zonomi": return zonomi.NewDNSProvider() + case "local": + return localserver.NewDNSProvider() default: return nil, fmt.Errorf("unrecognized DNS provider: %s", name) } diff --git a/providers/dns/localserver/localserver.go b/providers/dns/localserver/localserver.go new file mode 100644 index 0000000000..7e931bf87e --- /dev/null +++ b/providers/dns/localserver/localserver.go @@ -0,0 +1,111 @@ +package localserver + +import ( + "errors" + "log" + "net" + "net/netip" + "os" + + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/miekg/dns" +) + +// Environment variables names. +const ( + envNamespace = "LOCALSERVER_" + + EnvListen = envNamespace + "LISTEN" +) + +type DNSProvider struct { + addr string + server *dns.Server +} + +func NewDNSProvider() (*DNSProvider, error) { + addr := os.Getenv(EnvListen) + if addr == "" { + return nil, errors.New("localserver: listen addr is nil") + } + _, err := netip.ParseAddrPort(addr) + if err != nil { + return nil, err + } + return &DNSProvider{addr: addr}, nil +} + +func (s *DNSProvider) Present(domain, token, keyAuth string) error { + addr, err := netip.ParseAddrPort(s.addr) + if err != nil { + return err + } + + conn, err := net.ListenUDP("udp", net.UDPAddrFromAddrPort(addr)) + if err != nil { + return err + } + + fqdn, value := dns01.GetRecord(domain, keyAuth) + handler := &dnsHandler{acmeResponse: value, fqdn: fqdn} + s.server = &dns.Server{Handler: handler, PacketConn: conn} + + startCh := make(chan struct{}, 1) + errCh := make(chan error, 1) + s.server.NotifyStartedFunc = func() { + startCh <- struct{}{} + } + go func() { + err := s.server.ActivateAndServe() + if err != nil { + errCh <- err + return + } + }() + select { + case err := <-errCh: + return err + case <-startCh: + } + + return nil +} + +func (s *DNSProvider) CleanUp(domain, token, keyAuth string) error { + if s.server == nil { + return errors.New("dns server is not running") + } + err := s.server.Shutdown() + s.server = nil + return err +} + +type dnsHandler struct { + acmeResponse string + fqdn string +} + +func (h *dnsHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { + m := new(dns.Msg) + m.SetReply(r) + m.Authoritative = true + m.RecursionAvailable = false + question := r.Question[0] + if question.Name == h.fqdn && question.Qtype == dns.TypeTXT { + m.Answer = append(m.Answer, &dns.TXT{ + Hdr: dns.RR_Header{ + Name: question.Name, + Rrtype: dns.TypeTXT, + Class: dns.ClassINET, + }, + Txt: []string{h.acmeResponse}, + }) + log.Printf("localserver dns response: found, request: %#v\n", question) + } else { + m.Rcode = dns.RcodeNameError + log.Printf("localserver dns response: not found, request: %#v\n", question) + } + if err := w.WriteMsg(m); err != nil { + log.Println(err.Error()) + } +} diff --git a/providers/dns/localserver/localserver.toml b/providers/dns/localserver/localserver.toml new file mode 100644 index 0000000000..742e7aaf17 --- /dev/null +++ b/providers/dns/localserver/localserver.toml @@ -0,0 +1,13 @@ +Name = "Localserver" +Description = '''Setup localserver udp dns server that can serve forawrded requests from main dns server''' +Code = "localserver" +Since = "v0.0.1" + +Example = ''' +LOCALSERVER_LISTEN=0.0.0.0:5353 \ +lego --email myemail@example.com --dns localserver --domains my.example.org run +''' + +[Configuration] + [Configuration.Credentials] + LOCALSERVER_LISTEN = "Listen udp dns-server address" diff --git a/providers/dns/localserver/localserver_test.go b/providers/dns/localserver/localserver_test.go new file mode 100644 index 0000000000..a90b3c7ef6 --- /dev/null +++ b/providers/dns/localserver/localserver_test.go @@ -0,0 +1,45 @@ +package localserver + +import ( + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/miekg/dns" + "github.com/stretchr/testify/assert" + "testing" + + "github.com/go-acme/lego/v4/platform/tester" +) + +var envTest = tester.NewEnvTest(EnvListen) +var listenAddr = "127.0.0.1:5352" + +func TestDnsProvider(t *testing.T) { + envTest.Apply(map[string]string{EnvListen: listenAddr}) + provider, err := NewDNSProvider() + assert.Nil(t, err) + + domain := "foo.com" + keyAuth := "12d==" + + fqdn, txtExpectedValue := dns01.GetRecord(domain, keyAuth) + err = provider.Present(domain, "", keyAuth) + assert.Nil(t, err) + + r, err := makeDnsQuery(fqdn, dns.TypeTXT, listenAddr) + assert.Nil(t, err) + assert.Equal(t, txtExpectedValue, r.Answer[0].(*dns.TXT).Txt[0]) + + r, err = makeDnsQuery("bar.com.", dns.TypeTXT, listenAddr) + assert.Nil(t, err) + assert.Equal(t, dns.RcodeNameError, r.MsgHdr.Rcode) + + err = provider.CleanUp(domain, "", keyAuth) + assert.Nil(t, err) +} + +func makeDnsQuery(fqdn string, dnsType uint16, dnsServer string) (*dns.Msg, error) { + c := new(dns.Client) + m := new(dns.Msg) + m.SetQuestion(fqdn, dnsType) + r, _, err := c.Exchange(m, dnsServer) + return r, err +}