summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2020-02-13 23:14:18 +0000
committerMike Crute <mike@crute.us>2020-08-11 02:56:10 +0000
commitfa346cbe0ab89b3a3c02d8fae85dc5aa471b62a3 (patch)
treefbd2d45cdaab047ed93733399f9048716b479217
parent1010edcba255040f8e4140f45f9113969d79effb (diff)
downloadgo_ddns_manager-wip-more-management.tar.bz2
go_ddns_manager-wip-more-management.tar.xz
go_ddns_manager-wip-more-management.zip
WIP: Add more management controllerswip-more-management
-rw-r--r--bind/config.go15
-rw-r--r--bind/util.go17
-rw-r--r--main.go9
-rw-r--r--web/config.go22
-rw-r--r--web/controllers/dns_manage.go68
-rw-r--r--web/middleware/dns_manage.go33
6 files changed, 158 insertions, 6 deletions
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 {
33 return v 33 return v
34} 34}
35 35
36func (c *BINDConfig) ZonesInView(view string) []*Zone {
37 out := []*Zone{}
38
39 zm, ok := c.zones[view]
40 if !ok {
41 return out
42 }
43
44 for _, z := range zm {
45 out = append(out, z)
46 }
47
48 return out
49}
50
36func (c *BINDConfig) Zone(view, name string) *Zone { 51func (c *BINDConfig) Zone(view, name string) *Zone {
37 if !strings.HasSuffix(name, ".") { 52 if !strings.HasSuffix(name, ".") {
38 name = fmt.Sprintf("%s.", name) 53 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 @@
1package bind
2
3import (
4 "strings"
5)
6
7func DeCanonicalize(z string) string {
8 return strings.TrimRight(z, ".")
9}
10
11func Canonicalize(z string) string {
12 if !strings.HasSuffix(z, ".") {
13 return z + "."
14 } else {
15 return z
16 }
17}
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 {
33 acme.DELETE("/:id", controllers.DeleteAcmeChallenge) 33 acme.DELETE("/:id", controllers.DeleteAcmeChallenge)
34 } 34 }
35 35
36 dns := router.Group("/dns")
37 dns.Use(middleware.DnsManageAuthMiddleware)
38 {
39 dns.GET("", controllers.DnsManageRoot)
40 dns.GET("/view", controllers.DnsViews)
41 dns.GET("/zone", controllers.DnsZones)
42 dns.GET("/zone/:view/:name", controllers.DnsZoneDetails)
43 }
44
36 return router 45 return router
37} 46}
38 47
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 @@
1package web 1package web
2 2
3import ( 3import (
4 "crypto/subtle"
4 "encoding/json" 5 "encoding/json"
5 "io/ioutil" 6 "io/ioutil"
6 "strings" 7 "strings"
@@ -10,12 +11,13 @@ import (
10) 11)
11 12
12type ServerConfig struct { 13type ServerConfig struct {
13 BindConfig *bind.BINDConfig 14 BindConfig *bind.BINDConfig
14 DNSClient *dns.DNSClient 15 DNSClient *dns.DNSClient
15 AcmeView string 16 AcmeView string
16 DynamicDnsView string 17 DynamicDnsView string
17 DDNSSecrets map[string]string `json:"DDNS"` 18 DDNSSecrets map[string]string `json:"DDNS"`
18 AcmeSecrets map[string]map[string]int `json:"ACME"` 19 AcmeSecrets map[string]map[string]int `json:"ACME"`
20 DNSManageSecrets map[string]string `json:"DNS_MANAGE"`
19} 21}
20 22
21func LoadServerConfig(zonesFile, secretsFile, server, view string) (*ServerConfig, error) { 23func LoadServerConfig(zonesFile, secretsFile, server, view string) (*ServerConfig, error) {
@@ -53,6 +55,14 @@ func (s *ServerConfig) AcmeSecretExists(k string) bool {
53 return ok 55 return ok
54} 56}
55 57
58func (s *ServerConfig) DNSUserAuth(u, p string) bool {
59 cp, ok := s.DNSManageSecrets[u]
60 if !ok {
61 return false
62 }
63 return subtle.ConstantTimeCompare([]byte(p), []byte(cp)) == 1
64}
65
56func (s *ServerConfig) IsAcmeClientAllowed(key, zone string) bool { 66func (s *ServerConfig) IsAcmeClientAllowed(key, zone string) bool {
57 u, ok := s.AcmeSecrets[key] 67 u, ok := s.AcmeSecrets[key]
58 if !ok { 68 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 @@
1package controllers
2
3import (
4 "net/http"
5
6 "github.com/gin-gonic/gin"
7 "github.com/miekg/dns"
8
9 "code.crute.me/mcrute/go_ddns_manager/bind"
10 "code.crute.me/mcrute/go_ddns_manager/util"
11 "code.crute.me/mcrute/go_ddns_manager/web/middleware"
12)
13
14func DnsManageRoot(c *gin.Context) {
15 c.IndentedJSON(http.StatusOK, gin.H{
16 "_rels": gin.H{
17 "zones": util.MakeURL(c.Request, "/dns/zone").String(),
18 "views": util.MakeURL(c.Request, "/dns/view").String(),
19 },
20 })
21}
22
23func DnsViews(c *gin.Context) {
24 cfg := middleware.GetServerConfig(c)
25 out := map[string]map[string]string{}
26
27 for _, v := range cfg.BindConfig.Views() {
28 k := map[string]string{}
29 out[v] = k
30 for _, z := range cfg.BindConfig.ZonesInView(v) {
31 zn := bind.DeCanonicalize(z.Name)
32 k[zn] = util.MakeURL(c.Request, "/dns/zone/%s/%s", v, zn).String()
33 }
34 }
35
36 c.IndentedJSON(http.StatusOK, gin.H{"views": out})
37}
38
39func DnsZones(c *gin.Context) {
40
41}
42
43func DnsZoneDetails(c *gin.Context) {
44 cfg := middleware.GetServerConfig(c)
45
46 z := cfg.BindConfig.Zone(c.Param("view"), c.Param("name"))
47 if z == nil {
48 c.JSON(http.StatusNotFound, gin.H{
49 "error": "Zone not found",
50 })
51 return
52 }
53
54 envs := []*dns.Envelope{}
55 ec, err := cfg.DNSClient.AXFR(z)
56 if err != nil {
57 c.JSON(http.StatusInternalServerError, gin.H{
58 "error": err.Error(),
59 })
60 return
61 }
62
63 for e := range ec {
64 envs = append(envs, e)
65 }
66
67 c.JSON(http.StatusOK, envs)
68}
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 @@
1package middleware
2
3import (
4 "net/http"
5
6 "github.com/gin-gonic/gin"
7)
8
9const dnsUserId = "DNSManageUserID"
10
11func DnsManageAuthMiddleware(c *gin.Context) {
12 cfg := GetServerConfig(c)
13
14 user, pwd, ok := c.Request.BasicAuth()
15 if !ok {
16 c.Request.Header.Set("WWW-Authenticate", `Basic realm="closed site"`)
17 c.AbortWithStatus(http.StatusUnauthorized)
18 return
19 }
20
21 if !cfg.DNSUserAuth(user, pwd) {
22 c.AbortWithStatus(http.StatusForbidden)
23 return
24 } else {
25 c.Set(dnsUserId, user)
26 }
27
28 c.Next()
29}
30
31func GetDnsAuthContext(c *gin.Context) string {
32 return c.GetString(dnsUserId)
33}