diff options
author | Mike Crute <mike@crute.us> | 2020-01-04 00:28:25 +0000 |
---|---|---|
committer | Mike Crute <mike@crute.us> | 2020-01-04 00:28:25 +0000 |
commit | b4214b63d7c73cb0fec55b4e678a98327a159d48 (patch) | |
tree | bb5b160d62b25f483151958f498aac81f51898db | |
parent | 1a33a24e60ef985db446deb46b193ab57781ea0e (diff) | |
download | go_ddns_manager-b4214b63d7c73cb0fec55b4e678a98327a159d48.tar.bz2 go_ddns_manager-b4214b63d7c73cb0fec55b4e678a98327a159d48.tar.xz go_ddns_manager-b4214b63d7c73cb0fec55b4e678a98327a159d48.zip |
Refactor out of main a bit
-rw-r--r-- | bind/config.go | 19 | ||||
-rw-r--r-- | go.mod | 1 | ||||
-rw-r--r-- | go.sum | 5 | ||||
-rw-r--r-- | main.go | 177 | ||||
-rw-r--r-- | web.go | 26 |
5 files changed, 141 insertions, 87 deletions
diff --git a/bind/config.go b/bind/config.go index 584b1ee..42b97cf 100644 --- a/bind/config.go +++ b/bind/config.go | |||
@@ -54,6 +54,25 @@ func (c *BINDConfig) Zone(view, name string) *Zone { | |||
54 | return z | 54 | return z |
55 | } | 55 | } |
56 | 56 | ||
57 | // Find the closest zone that we manage by striping dotted components off the | ||
58 | // front of the domain until one matches. If there is a match return the zone | ||
59 | // that matched and any prefix components, if any, as a dotted string. If none | ||
60 | // match then return nil. | ||
61 | func (c *BINDConfig) FindClosestZone(zoneIn, view string) (*Zone, string) { | ||
62 | suffix := "" | ||
63 | prefix := []string{} | ||
64 | |||
65 | zc := strings.Split(zoneIn, ".") | ||
66 | for i := 0; i <= len(zc)-2; i++ { | ||
67 | prefix, suffix = zc[:i], strings.Join(zc[i:], ".") | ||
68 | if zone := c.Zone(view, suffix); zone != nil { | ||
69 | return zone, strings.Join(prefix, ".") | ||
70 | } | ||
71 | } | ||
72 | |||
73 | return nil, "" | ||
74 | } | ||
75 | |||
57 | func (c *BINDConfig) AddKey(k *Key) { | 76 | func (c *BINDConfig) AddKey(k *Key) { |
58 | c.keys[k.Name] = k | 77 | c.keys[k.Name] = k |
59 | } | 78 | } |
@@ -5,4 +5,5 @@ go 1.13 | |||
5 | require ( | 5 | require ( |
6 | github.com/gin-gonic/gin v1.5.0 | 6 | github.com/gin-gonic/gin v1.5.0 |
7 | github.com/miekg/dns v1.1.26 | 7 | github.com/miekg/dns v1.1.26 |
8 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect | ||
8 | ) | 9 | ) |
@@ -1,4 +1,5 @@ | |||
1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
3 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= | 4 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= |
4 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= | 5 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= |
@@ -20,9 +21,11 @@ github.com/miekg/dns v1.1.26 h1:gPxPSwALAeHJSjarOs00QjVdV9QoBvc1D2ujQUr5BzU= | |||
20 | github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= | 21 | github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= |
21 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= | 22 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= |
22 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= | 23 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= |
24 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
23 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | 25 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
24 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | 26 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
25 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= | 27 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= |
28 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= | ||
26 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | 29 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= |
27 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= | 30 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= |
28 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= | 31 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= |
@@ -44,6 +47,8 @@ golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPT | |||
44 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | 47 | golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
45 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | 48 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
46 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= | 49 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= |
50 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= | ||
51 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||
47 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | 52 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
48 | golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= | 53 | golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= |
49 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | 54 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
@@ -3,12 +3,10 @@ package main | |||
3 | import ( | 3 | import ( |
4 | "encoding/base64" | 4 | "encoding/base64" |
5 | "encoding/json" | 5 | "encoding/json" |
6 | "fmt" | ||
7 | "io/ioutil" | 6 | "io/ioutil" |
8 | "log" | 7 | "log" |
9 | "net" | 8 | "net" |
10 | "net/http" | 9 | "net/http" |
11 | "net/url" | ||
12 | "regexp" | 10 | "regexp" |
13 | "strings" | 11 | "strings" |
14 | 12 | ||
@@ -18,41 +16,58 @@ import ( | |||
18 | ) | 16 | ) |
19 | 17 | ||
20 | const ( | 18 | const ( |
21 | ACME_AUTH_KEY = "ACMEAuthUserID" | 19 | ACME_AUTH_KEY = "ACMEAuthUserID" |
22 | DDNS_AUTH_KEY = "DDNSAuthZone" | 20 | DDNS_AUTH_KEY = "DDNSAuthZone" |
21 | CTX_SERVER_CONFIG = "ServerConfig" | ||
23 | ) | 22 | ) |
24 | 23 | ||
25 | var ( | 24 | var ( |
26 | cfg *bind.BINDConfig | ||
27 | secrets *Secrets | ||
28 | ipRegexp = regexp.MustCompile(`(?:\[([0-9a-f:]+)\]|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})):\d+`) | 25 | ipRegexp = regexp.MustCompile(`(?:\[([0-9a-f:]+)\]|(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})):\d+`) |
29 | ) | 26 | ) |
30 | 27 | ||
31 | func init() { | 28 | type ServerConfig struct { |
32 | var err error | 29 | BindConfig *bind.BINDConfig |
33 | cfg, err = bind.ParseBINDConfig("zones.conf") | 30 | DNSClient *dns.DNSClient |
31 | ddnsSecrets map[string]string `json:"DDNS"` | ||
32 | acmeSecrets map[string]map[string]int `json:"ACME"` | ||
33 | } | ||
34 | |||
35 | func LoadServerConfig(zonesFile, secretsFile string) (*ServerConfig, error) { | ||
36 | scfg := &ServerConfig{ | ||
37 | // TODO: Remove | ||
38 | DNSClient: &dns.DNSClient{Server: "172.16.18.52:53"}, | ||
39 | } | ||
40 | |||
41 | cfg, err := bind.ParseBINDConfig(zonesFile) | ||
34 | if err != nil { | 42 | if err != nil { |
35 | panic(err) | 43 | return nil, err |
36 | } | 44 | } |
45 | scfg.BindConfig = cfg | ||
37 | 46 | ||
38 | fd, err := ioutil.ReadFile("secrets.json") | 47 | fd, err := ioutil.ReadFile(secretsFile) |
39 | if err != nil { | 48 | if err != nil { |
40 | panic(err) | 49 | return nil, err |
41 | } | 50 | } |
42 | 51 | ||
43 | secrets = &Secrets{} | 52 | if err = json.Unmarshal(fd, scfg); err != nil { |
44 | if err = json.Unmarshal(fd, secrets); err != nil { | 53 | return nil, err |
45 | panic(err) | ||
46 | } | 54 | } |
55 | |||
56 | return scfg, nil | ||
57 | } | ||
58 | |||
59 | func (s *ServerConfig) GetDDNZoneName(k string) string { | ||
60 | v, _ := s.ddnsSecrets[k] | ||
61 | return v | ||
47 | } | 62 | } |
48 | 63 | ||
49 | type Secrets struct { | 64 | func (s *ServerConfig) AcmeSecretExists(k string) bool { |
50 | DDNS map[string]string | 65 | _, ok := s.acmeSecrets[k] |
51 | ACME map[string]map[string]int | 66 | return ok |
52 | } | 67 | } |
53 | 68 | ||
54 | func (s *Secrets) IsACMEClientAllowed(key, zone string) bool { | 69 | func (s *ServerConfig) IsAcmeClientAllowed(key, zone string) bool { |
55 | u, ok := s.ACME[key] | 70 | u, ok := s.acmeSecrets[key] |
56 | if !ok { | 71 | if !ok { |
57 | return false | 72 | return false |
58 | } | 73 | } |
@@ -74,12 +89,12 @@ type DDNSUpdateRequest struct { | |||
74 | Key string `form:"key" binding:"required"` | 89 | Key string `form:"key" binding:"required"` |
75 | } | 90 | } |
76 | 91 | ||
77 | type ACMEChallenge struct { | 92 | type AcmeChallenge struct { |
78 | Zone string `json:"zone" binding:"required"` | 93 | Zone string `json:"zone" binding:"required"` |
79 | Challenge string `json:"challenge" binding:"required"` | 94 | Challenge string `json:"challenge" binding:"required"` |
80 | } | 95 | } |
81 | 96 | ||
82 | type ACMEChallengeID struct { | 97 | type AcmeChallengeID struct { |
83 | Zone string | 98 | Zone string |
84 | Prefix string | 99 | Prefix string |
85 | Challenge string | 100 | Challenge string |
@@ -95,49 +110,10 @@ func joinDomainParts(parts ...string) string { | |||
95 | return strings.Join(p, ".") | 110 | return strings.Join(p, ".") |
96 | } | 111 | } |
97 | 112 | ||
98 | // Find the closest zone that we manage by striping dotted components off the | ||
99 | // front of the domain until one matches. If there is a match return the zone | ||
100 | // that matched and any prefix components, if any, as a dotted string. If none | ||
101 | // match then return nil. | ||
102 | func findClosestZone(cfg *bind.BINDConfig, zoneIn, view string) (*bind.Zone, string) { | ||
103 | suffix := "" | ||
104 | prefix := []string{} | ||
105 | |||
106 | zc := strings.Split(zoneIn, ".") | ||
107 | for i := 0; i <= len(zc)-2; i++ { | ||
108 | prefix, suffix = zc[:i], strings.Join(zc[i:], ".") | ||
109 | if zone := cfg.Zone(view, suffix); zone != nil { | ||
110 | return zone, strings.Join(prefix, ".") | ||
111 | } | ||
112 | } | ||
113 | |||
114 | return nil, "" | ||
115 | } | ||
116 | |||
117 | func makeURL(r *http.Request, path string, subs ...interface{}) *url.URL { | ||
118 | scheme := "https" | ||
119 | |||
120 | if r.TLS == nil { | ||
121 | scheme = "http" | ||
122 | } | ||
123 | |||
124 | // Always defer to whatever the proxy told us it was doing because this | ||
125 | // could be a mullet-VIP in either direction. | ||
126 | if fwProto := r.Header.Get("X-Forwarded-Proto"); fwProto != "" { | ||
127 | scheme = fwProto | ||
128 | } | ||
129 | |||
130 | return &url.URL{ | ||
131 | Scheme: scheme, | ||
132 | Host: r.Host, | ||
133 | Path: fmt.Sprintf(path, subs...), | ||
134 | } | ||
135 | } | ||
136 | |||
137 | func createAcmeChallenge(c *gin.Context) { | 113 | func createAcmeChallenge(c *gin.Context) { |
138 | dc := dns.DNSClient{Server: "172.16.18.52:53"} | 114 | cfg := GetServerConfig(c) |
139 | 115 | ||
140 | var ch ACMEChallenge | 116 | var ch AcmeChallenge |
141 | if err := c.ShouldBindJSON(&ch); err != nil { | 117 | if err := c.ShouldBindJSON(&ch); err != nil { |
142 | c.JSON(http.StatusBadRequest, gin.H{ | 118 | c.JSON(http.StatusBadRequest, gin.H{ |
143 | "error": err.Error(), | 119 | "error": err.Error(), |
@@ -145,7 +121,7 @@ func createAcmeChallenge(c *gin.Context) { | |||
145 | return | 121 | return |
146 | } | 122 | } |
147 | 123 | ||
148 | zone, prefix := findClosestZone(cfg, ch.Zone, "external") | 124 | zone, prefix := cfg.BindConfig.FindClosestZone(ch.Zone, "external") |
149 | if zone == nil { | 125 | if zone == nil { |
150 | c.JSON(http.StatusNotFound, gin.H{ | 126 | c.JSON(http.StatusNotFound, gin.H{ |
151 | "error": "Zone not found", | 127 | "error": "Zone not found", |
@@ -153,7 +129,7 @@ func createAcmeChallenge(c *gin.Context) { | |||
153 | return | 129 | return |
154 | } | 130 | } |
155 | 131 | ||
156 | if v := c.GetString(ACME_AUTH_KEY); !secrets.IsACMEClientAllowed(v, zone.Name) { | 132 | if v := c.GetString(ACME_AUTH_KEY); !cfg.IsAcmeClientAllowed(v, zone.Name) { |
157 | c.JSON(http.StatusForbidden, gin.H{ | 133 | c.JSON(http.StatusForbidden, gin.H{ |
158 | "error": "Zone update not allowed", | 134 | "error": "Zone update not allowed", |
159 | }) | 135 | }) |
@@ -161,7 +137,7 @@ func createAcmeChallenge(c *gin.Context) { | |||
161 | } | 137 | } |
162 | 138 | ||
163 | // Do this first, in-case it fails (even though it should never fail) | 139 | // Do this first, in-case it fails (even though it should never fail) |
164 | id, err := json.Marshal(ACMEChallengeID{ | 140 | id, err := json.Marshal(AcmeChallengeID{ |
165 | Zone: zone.Name, | 141 | Zone: zone.Name, |
166 | Prefix: prefix, | 142 | Prefix: prefix, |
167 | Challenge: ch.Challenge, | 143 | Challenge: ch.Challenge, |
@@ -183,7 +159,7 @@ func createAcmeChallenge(c *gin.Context) { | |||
183 | } | 159 | } |
184 | 160 | ||
185 | // Cleanup any old challenges before adding a new one | 161 | // Cleanup any old challenges before adding a new one |
186 | if err := dc.RemoveAll(zone, t); err != nil { | 162 | if err := cfg.DNSClient.RemoveAll(zone, t); err != nil { |
187 | log.Printf("error RemoveAll: %s", err) | 163 | log.Printf("error RemoveAll: %s", err) |
188 | c.JSON(http.StatusInternalServerError, gin.H{ | 164 | c.JSON(http.StatusInternalServerError, gin.H{ |
189 | "error": err.Error(), | 165 | "error": err.Error(), |
@@ -191,7 +167,7 @@ func createAcmeChallenge(c *gin.Context) { | |||
191 | return | 167 | return |
192 | } | 168 | } |
193 | 169 | ||
194 | if err := dc.Insert(zone, t); err != nil { | 170 | if err := cfg.DNSClient.Insert(zone, t); err != nil { |
195 | log.Printf("error Insert: %s", err) | 171 | log.Printf("error Insert: %s", err) |
196 | c.JSON(http.StatusInternalServerError, gin.H{ | 172 | c.JSON(http.StatusInternalServerError, gin.H{ |
197 | "error": err.Error(), | 173 | "error": err.Error(), |
@@ -206,7 +182,7 @@ func createAcmeChallenge(c *gin.Context) { | |||
206 | } | 182 | } |
207 | 183 | ||
208 | func deleteAcmeChallenge(c *gin.Context) { | 184 | func deleteAcmeChallenge(c *gin.Context) { |
209 | dc := dns.DNSClient{Server: "172.16.18.52:53"} | 185 | cfg := GetServerConfig(c) |
210 | 186 | ||
211 | rid, err := base64.URLEncoding.DecodeString(c.Param("id")) | 187 | rid, err := base64.URLEncoding.DecodeString(c.Param("id")) |
212 | if err != nil { | 188 | if err != nil { |
@@ -216,7 +192,7 @@ func deleteAcmeChallenge(c *gin.Context) { | |||
216 | return | 192 | return |
217 | } | 193 | } |
218 | 194 | ||
219 | var id ACMEChallengeID | 195 | var id AcmeChallengeID |
220 | if err = json.Unmarshal(rid, &id); err != nil { | 196 | if err = json.Unmarshal(rid, &id); err != nil { |
221 | c.JSON(http.StatusBadRequest, gin.H{ | 197 | c.JSON(http.StatusBadRequest, gin.H{ |
222 | "error": "unable to decode ID", | 198 | "error": "unable to decode ID", |
@@ -224,7 +200,7 @@ func deleteAcmeChallenge(c *gin.Context) { | |||
224 | return | 200 | return |
225 | } | 201 | } |
226 | 202 | ||
227 | zone := cfg.Zone("external", id.Zone) | 203 | zone := cfg.BindConfig.Zone("external", id.Zone) |
228 | if zone == nil { | 204 | if zone == nil { |
229 | c.JSON(http.StatusNotFound, gin.H{ | 205 | c.JSON(http.StatusNotFound, gin.H{ |
230 | "error": "Zone not found", | 206 | "error": "Zone not found", |
@@ -232,7 +208,7 @@ func deleteAcmeChallenge(c *gin.Context) { | |||
232 | return | 208 | return |
233 | } | 209 | } |
234 | 210 | ||
235 | if v := c.GetString(ACME_AUTH_KEY); !secrets.IsACMEClientAllowed(v, zone.Name) { | 211 | if v := c.GetString(ACME_AUTH_KEY); !cfg.IsAcmeClientAllowed(v, zone.Name) { |
236 | c.JSON(http.StatusForbidden, gin.H{ | 212 | c.JSON(http.StatusForbidden, gin.H{ |
237 | "error": "Zone update not allowed", | 213 | "error": "Zone update not allowed", |
238 | }) | 214 | }) |
@@ -245,7 +221,7 @@ func deleteAcmeChallenge(c *gin.Context) { | |||
245 | Txt: []string{id.Challenge}, | 221 | Txt: []string{id.Challenge}, |
246 | } | 222 | } |
247 | 223 | ||
248 | if err := dc.Remove(zone, t); err != nil { | 224 | if err := cfg.DNSClient.Remove(zone, t); err != nil { |
249 | log.Printf("error Remove: %s", err) | 225 | log.Printf("error Remove: %s", err) |
250 | c.JSON(http.StatusInternalServerError, gin.H{ | 226 | c.JSON(http.StatusInternalServerError, gin.H{ |
251 | "error": err.Error(), | 227 | "error": err.Error(), |
@@ -257,7 +233,7 @@ func deleteAcmeChallenge(c *gin.Context) { | |||
257 | } | 233 | } |
258 | 234 | ||
259 | func updateDynamicDNS(c *gin.Context) { | 235 | func updateDynamicDNS(c *gin.Context) { |
260 | dc := dns.DNSClient{Server: "172.16.18.52:53"} | 236 | cfg := GetServerConfig(c) |
261 | 237 | ||
262 | res := c.GetString(DDNS_AUTH_KEY) | 238 | res := c.GetString(DDNS_AUTH_KEY) |
263 | if res == "" { | 239 | if res == "" { |
@@ -266,7 +242,7 @@ func updateDynamicDNS(c *gin.Context) { | |||
266 | return | 242 | return |
267 | } | 243 | } |
268 | 244 | ||
269 | zone, part := findClosestZone(cfg, res, "external") | 245 | zone, part := cfg.BindConfig.FindClosestZone(res, "external") |
270 | if zone == nil { | 246 | if zone == nil { |
271 | log.Println("ddns: Unable to locate zone") | 247 | log.Println("ddns: Unable to locate zone") |
272 | c.AbortWithStatus(http.StatusNotFound) | 248 | c.AbortWithStatus(http.StatusNotFound) |
@@ -292,13 +268,13 @@ func updateDynamicDNS(c *gin.Context) { | |||
292 | } | 268 | } |
293 | 269 | ||
294 | // Cleanup any old records before adding the new one | 270 | // Cleanup any old records before adding the new one |
295 | if err := dc.RemoveAll(zone, t); err != nil { | 271 | if err := cfg.DNSClient.RemoveAll(zone, t); err != nil { |
296 | log.Printf("ddns RemoveAll: %s", err) | 272 | log.Printf("ddns RemoveAll: %s", err) |
297 | c.AbortWithStatus(http.StatusInternalServerError) | 273 | c.AbortWithStatus(http.StatusInternalServerError) |
298 | return | 274 | return |
299 | } | 275 | } |
300 | 276 | ||
301 | if err := dc.Insert(zone, t); err != nil { | 277 | if err := cfg.DNSClient.Insert(zone, t); err != nil { |
302 | log.Printf("ddns Insert: %s", err) | 278 | log.Printf("ddns Insert: %s", err) |
303 | c.AbortWithStatus(http.StatusInternalServerError) | 279 | c.AbortWithStatus(http.StatusInternalServerError) |
304 | return | 280 | return |
@@ -330,15 +306,17 @@ func reflectIP(c *gin.Context) { | |||
330 | } | 306 | } |
331 | } | 307 | } |
332 | 308 | ||
333 | func acmeAuth(c *gin.Context) { | 309 | func acmeAuthMiddleware(c *gin.Context) { |
310 | cfg := GetServerConfig(c) | ||
311 | |||
334 | _, pwd, ok := c.Request.BasicAuth() | 312 | _, pwd, ok := c.Request.BasicAuth() |
335 | if !ok { | 313 | if !ok { |
336 | c.Request.Header["WWW-Authenticate"] = []string{`Basic realm="closed site"`} | 314 | c.Request.Header.Set("WWW-Authenticate", `Basic realm="closed site"`) |
337 | c.AbortWithStatus(http.StatusUnauthorized) | 315 | c.AbortWithStatus(http.StatusUnauthorized) |
338 | return | 316 | return |
339 | } | 317 | } |
340 | 318 | ||
341 | if _, ok := secrets.ACME[pwd]; !ok { | 319 | if !cfg.AcmeSecretExists(pwd) { |
342 | c.AbortWithStatus(http.StatusForbidden) | 320 | c.AbortWithStatus(http.StatusForbidden) |
343 | return | 321 | return |
344 | } else { | 322 | } else { |
@@ -348,17 +326,19 @@ func acmeAuth(c *gin.Context) { | |||
348 | c.Next() | 326 | c.Next() |
349 | } | 327 | } |
350 | 328 | ||
351 | func ddnsAuth(c *gin.Context) { | 329 | func ddnsAuthMiddleware(c *gin.Context) { |
330 | cfg := GetServerConfig(c) | ||
331 | |||
352 | var req DDNSUpdateRequest | 332 | var req DDNSUpdateRequest |
353 | if err := c.ShouldBind(&req); err != nil { | 333 | if err := c.ShouldBind(&req); err != nil { |
354 | log.Println("ddnsAuth: No key in request") | 334 | log.Println("ddnsAuthMiddleware: No key in request") |
355 | c.AbortWithStatus(http.StatusNotFound) | 335 | c.AbortWithStatus(http.StatusNotFound) |
356 | return | 336 | return |
357 | } | 337 | } |
358 | 338 | ||
359 | res, ok := secrets.DDNS[req.Key] | 339 | res := cfg.GetDDNZoneName(req.Key) |
360 | if !ok { | 340 | if res == "" { |
361 | log.Println("ddnsAuth: Unknown secret") | 341 | log.Println("ddnsAuthMiddleware: Unknown secret") |
362 | c.AbortWithStatus(http.StatusNotFound) | 342 | c.AbortWithStatus(http.StatusNotFound) |
363 | return | 343 | return |
364 | } else { | 344 | } else { |
@@ -368,21 +348,44 @@ func ddnsAuth(c *gin.Context) { | |||
368 | c.Next() | 348 | c.Next() |
369 | } | 349 | } |
370 | 350 | ||
351 | func ConfigContextMiddleware(cfg *ServerConfig) func(*gin.Context) { | ||
352 | return func(c *gin.Context) { | ||
353 | c.Set(CTX_SERVER_CONFIG, cfg) | ||
354 | c.Next() | ||
355 | } | ||
356 | } | ||
357 | |||
358 | func GetServerConfig(c *gin.Context) *ServerConfig { | ||
359 | v, ok := c.Get(CTX_SERVER_CONFIG) | ||
360 | if !ok { | ||
361 | // This should never happen if the config context middlware is in place | ||
362 | panic("Unable to get config from request") | ||
363 | } | ||
364 | |||
365 | return v.(*ServerConfig) | ||
366 | } | ||
367 | |||
371 | func main() { | 368 | func main() { |
372 | gin.SetMode(gin.DebugMode) | 369 | gin.SetMode(gin.DebugMode) |
373 | 370 | ||
371 | cfg, err := LoadServerConfig("cfg/zones.conf", "cfg/secrets.json") | ||
372 | if err != nil { | ||
373 | panic(err) | ||
374 | } | ||
375 | |||
374 | router := gin.Default() | 376 | router := gin.Default() |
377 | router.Use(ConfigContextMiddleware(cfg)) | ||
375 | 378 | ||
376 | router.GET("/reflect-ip", reflectIP) | 379 | router.GET("/reflect-ip", reflectIP) |
377 | 380 | ||
378 | ddns := router.Group("/dynamic-dns") | 381 | ddns := router.Group("/dynamic-dns") |
379 | ddns.Use(ddnsAuth) | 382 | ddns.Use(ddnsAuthMiddleware) |
380 | { | 383 | { |
381 | ddns.POST("", updateDynamicDNS) | 384 | ddns.POST("", updateDynamicDNS) |
382 | } | 385 | } |
383 | 386 | ||
384 | acme := router.Group("/acme") | 387 | acme := router.Group("/acme") |
385 | acme.Use(acmeAuth) | 388 | acme.Use(acmeAuthMiddleware) |
386 | { | 389 | { |
387 | acme.POST("", createAcmeChallenge) | 390 | acme.POST("", createAcmeChallenge) |
388 | acme.DELETE("/:id", deleteAcmeChallenge) | 391 | acme.DELETE("/:id", deleteAcmeChallenge) |
@@ -0,0 +1,26 @@ | |||
1 | package main | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | "net/http" | ||
6 | "net/url" | ||
7 | ) | ||
8 | |||
9 | func makeURL(r *http.Request, path string, subs ...interface{}) *url.URL { | ||
10 | scheme := "https" | ||
11 | if r.TLS == nil { | ||
12 | scheme = "http" | ||
13 | } | ||
14 | |||
15 | // Always defer to whatever the proxy told us it was doing because this | ||
16 | // could be a mullet-VIP in either direction. | ||
17 | if fwProto := r.Header.Get("X-Forwarded-Proto"); fwProto != "" { | ||
18 | scheme = fwProto | ||
19 | } | ||
20 | |||
21 | return &url.URL{ | ||
22 | Scheme: scheme, | ||
23 | Host: r.Host, | ||
24 | Path: fmt.Sprintf(path, subs...), | ||
25 | } | ||
26 | } | ||