From fa346cbe0ab89b3a3c02d8fae85dc5aa471b62a3 Mon Sep 17 00:00:00 2001 From: Mike Crute Date: Thu, 13 Feb 2020 23:14:18 +0000 Subject: WIP: Add more management controllers --- bind/config.go | 15 ++++++++++ bind/util.go | 17 +++++++++++ main.go | 9 ++++++ web/config.go | 22 ++++++++++---- web/controllers/dns_manage.go | 68 +++++++++++++++++++++++++++++++++++++++++++ web/middleware/dns_manage.go | 33 +++++++++++++++++++++ 6 files changed, 158 insertions(+), 6 deletions(-) create mode 100644 bind/util.go create mode 100644 web/controllers/dns_manage.go create mode 100644 web/middleware/dns_manage.go diff --git a/bind/config.go b/bind/config.go index 42b97cf..9d630f1 100644 --- a/bind/config.go +++ b/bind/config.go @@ -33,6 +33,21 @@ func (c *BINDConfig) Views() []string { return v } +func (c *BINDConfig) ZonesInView(view string) []*Zone { + out := []*Zone{} + + zm, ok := c.zones[view] + if !ok { + return out + } + + for _, z := range zm { + out = append(out, z) + } + + return out +} + func (c *BINDConfig) Zone(view, name string) *Zone { if !strings.HasSuffix(name, ".") { name = fmt.Sprintf("%s.", name) diff --git a/bind/util.go b/bind/util.go new file mode 100644 index 0000000..cc0ef23 --- /dev/null +++ b/bind/util.go @@ -0,0 +1,17 @@ +package bind + +import ( + "strings" +) + +func DeCanonicalize(z string) string { + return strings.TrimRight(z, ".") +} + +func Canonicalize(z string) string { + if !strings.HasSuffix(z, ".") { + return z + "." + } else { + return z + } +} diff --git a/main.go b/main.go index 5750da1..7bcdd2b 100644 --- a/main.go +++ b/main.go @@ -33,6 +33,15 @@ func makeServer(cfg *web.ServerConfig) *gin.Engine { acme.DELETE("/:id", controllers.DeleteAcmeChallenge) } + dns := router.Group("/dns") + dns.Use(middleware.DnsManageAuthMiddleware) + { + dns.GET("", controllers.DnsManageRoot) + dns.GET("/view", controllers.DnsViews) + dns.GET("/zone", controllers.DnsZones) + dns.GET("/zone/:view/:name", controllers.DnsZoneDetails) + } + return router } diff --git a/web/config.go b/web/config.go index 2479e38..acdc4bf 100644 --- a/web/config.go +++ b/web/config.go @@ -1,6 +1,7 @@ package web import ( + "crypto/subtle" "encoding/json" "io/ioutil" "strings" @@ -10,12 +11,13 @@ import ( ) type ServerConfig struct { - BindConfig *bind.BINDConfig - DNSClient *dns.DNSClient - AcmeView string - DynamicDnsView string - DDNSSecrets map[string]string `json:"DDNS"` - AcmeSecrets map[string]map[string]int `json:"ACME"` + BindConfig *bind.BINDConfig + DNSClient *dns.DNSClient + AcmeView string + DynamicDnsView string + DDNSSecrets map[string]string `json:"DDNS"` + AcmeSecrets map[string]map[string]int `json:"ACME"` + DNSManageSecrets map[string]string `json:"DNS_MANAGE"` } func LoadServerConfig(zonesFile, secretsFile, server, view string) (*ServerConfig, error) { @@ -53,6 +55,14 @@ func (s *ServerConfig) AcmeSecretExists(k string) bool { return ok } +func (s *ServerConfig) DNSUserAuth(u, p string) bool { + cp, ok := s.DNSManageSecrets[u] + if !ok { + return false + } + return subtle.ConstantTimeCompare([]byte(p), []byte(cp)) == 1 +} + func (s *ServerConfig) IsAcmeClientAllowed(key, zone string) bool { u, ok := s.AcmeSecrets[key] if !ok { diff --git a/web/controllers/dns_manage.go b/web/controllers/dns_manage.go new file mode 100644 index 0000000..6db0b13 --- /dev/null +++ b/web/controllers/dns_manage.go @@ -0,0 +1,68 @@ +package controllers + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/miekg/dns" + + "code.crute.me/mcrute/go_ddns_manager/bind" + "code.crute.me/mcrute/go_ddns_manager/util" + "code.crute.me/mcrute/go_ddns_manager/web/middleware" +) + +func DnsManageRoot(c *gin.Context) { + c.IndentedJSON(http.StatusOK, gin.H{ + "_rels": gin.H{ + "zones": util.MakeURL(c.Request, "/dns/zone").String(), + "views": util.MakeURL(c.Request, "/dns/view").String(), + }, + }) +} + +func DnsViews(c *gin.Context) { + cfg := middleware.GetServerConfig(c) + out := map[string]map[string]string{} + + for _, v := range cfg.BindConfig.Views() { + k := map[string]string{} + out[v] = k + for _, z := range cfg.BindConfig.ZonesInView(v) { + zn := bind.DeCanonicalize(z.Name) + k[zn] = util.MakeURL(c.Request, "/dns/zone/%s/%s", v, zn).String() + } + } + + c.IndentedJSON(http.StatusOK, gin.H{"views": out}) +} + +func DnsZones(c *gin.Context) { + +} + +func DnsZoneDetails(c *gin.Context) { + cfg := middleware.GetServerConfig(c) + + z := cfg.BindConfig.Zone(c.Param("view"), c.Param("name")) + if z == nil { + c.JSON(http.StatusNotFound, gin.H{ + "error": "Zone not found", + }) + return + } + + envs := []*dns.Envelope{} + ec, err := cfg.DNSClient.AXFR(z) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{ + "error": err.Error(), + }) + return + } + + for e := range ec { + envs = append(envs, e) + } + + c.JSON(http.StatusOK, envs) +} diff --git a/web/middleware/dns_manage.go b/web/middleware/dns_manage.go new file mode 100644 index 0000000..d3b420b --- /dev/null +++ b/web/middleware/dns_manage.go @@ -0,0 +1,33 @@ +package middleware + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +const dnsUserId = "DNSManageUserID" + +func DnsManageAuthMiddleware(c *gin.Context) { + cfg := GetServerConfig(c) + + user, pwd, ok := c.Request.BasicAuth() + if !ok { + c.Request.Header.Set("WWW-Authenticate", `Basic realm="closed site"`) + c.AbortWithStatus(http.StatusUnauthorized) + return + } + + if !cfg.DNSUserAuth(user, pwd) { + c.AbortWithStatus(http.StatusForbidden) + return + } else { + c.Set(dnsUserId, user) + } + + c.Next() +} + +func GetDnsAuthContext(c *gin.Context) string { + return c.GetString(dnsUserId) +} -- cgit v1.2.3