diff options
author | Mike Crute <mike@crute.us> | 2020-02-13 23:14:18 +0000 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2020-08-11 02:56:10 +0000 |
commit | fa346cbe0ab89b3a3c02d8fae85dc5aa471b62a3 (patch) | |
tree | fbd2d45cdaab047ed93733399f9048716b479217 | |
parent | 1010edcba255040f8e4140f45f9113969d79effb (diff) | |
download | go_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.go | 15 | ||||
-rw-r--r-- | bind/util.go | 17 | ||||
-rw-r--r-- | main.go | 9 | ||||
-rw-r--r-- | web/config.go | 22 | ||||
-rw-r--r-- | web/controllers/dns_manage.go | 68 | ||||
-rw-r--r-- | web/middleware/dns_manage.go | 33 |
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 | ||
36 | func (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 | |||
36 | func (c *BINDConfig) Zone(view, name string) *Zone { | 51 | func (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 @@ | |||
1 | package bind | ||
2 | |||
3 | import ( | ||
4 | "strings" | ||
5 | ) | ||
6 | |||
7 | func DeCanonicalize(z string) string { | ||
8 | return strings.TrimRight(z, ".") | ||
9 | } | ||
10 | |||
11 | func Canonicalize(z string) string { | ||
12 | if !strings.HasSuffix(z, ".") { | ||
13 | return z + "." | ||
14 | } else { | ||
15 | return z | ||
16 | } | ||
17 | } | ||
@@ -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 @@ | |||
1 | package web | 1 | package web |
2 | 2 | ||
3 | import ( | 3 | import ( |
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 | ||
12 | type ServerConfig struct { | 13 | type 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 | ||
21 | func LoadServerConfig(zonesFile, secretsFile, server, view string) (*ServerConfig, error) { | 23 | func 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 | ||
58 | func (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 | |||
56 | func (s *ServerConfig) IsAcmeClientAllowed(key, zone string) bool { | 66 | func (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 @@ | |||
1 | package controllers | ||
2 | |||
3 | import ( | ||
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 | |||
14 | func 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 | |||
23 | func 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 | |||
39 | func DnsZones(c *gin.Context) { | ||
40 | |||
41 | } | ||
42 | |||
43 | func 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 @@ | |||
1 | package middleware | ||
2 | |||
3 | import ( | ||
4 | "net/http" | ||
5 | |||
6 | "github.com/gin-gonic/gin" | ||
7 | ) | ||
8 | |||
9 | const dnsUserId = "DNSManageUserID" | ||
10 | |||
11 | func 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 | |||
31 | func GetDnsAuthContext(c *gin.Context) string { | ||
32 | return c.GetString(dnsUserId) | ||
33 | } | ||