forked from joohoi/acme-dns
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
260 lines (230 loc) · 6.79 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
//go:build !test
// +build !test
package main
import (
"context"
"crypto/tls"
"flag"
stdlog "log"
"net"
"net/http"
"os"
"strings"
"syscall"
"github.com/caddyserver/certmagic"
legolog "github.com/go-acme/lego/v3/log"
"github.com/julienschmidt/httprouter"
"github.com/pires/go-proxyproto"
"github.com/rs/cors"
log "github.com/sirupsen/logrus"
)
func main() {
// Created files are not world writable
syscall.Umask(0077)
configPtr := flag.String("c", "/etc/acme-dns/config.cfg", "config file location")
flag.Parse()
// Read global config
var err error
if fileIsAccessible(*configPtr) {
log.WithFields(log.Fields{"file": *configPtr}).Info("Using config file")
Config, err = readConfig(*configPtr)
} else if fileIsAccessible("./config.cfg") {
log.WithFields(log.Fields{"file": "./config.cfg"}).Info("Using config file")
Config, err = readConfig("./config.cfg")
} else {
log.Errorf("Configuration file not found.")
os.Exit(1)
}
if err != nil {
log.Errorf("Encountered an error while trying to read configuration file: %s", err)
os.Exit(1)
}
setupLogging(Config.Logconfig.Format, Config.Logconfig.Level)
// Open database
newDB := new(acmedb)
err = newDB.Init(Config.Database.Engine, Config.Database.Connection)
if err != nil {
log.Errorf("Could not open database [%v]", err)
os.Exit(1)
} else {
log.Info("Connected to database")
}
DB = newDB
defer DB.Close()
// Error channel for servers
errChan := make(chan error, 1)
// DNS server
dnsservers := make([]*DNSServer, 0)
if strings.HasPrefix(Config.General.Proto, "both") {
// Handle the case where DNS server should be started for both udp and tcp
udpProto := "udp"
tcpProto := "tcp"
if strings.HasSuffix(Config.General.Proto, "4") {
udpProto += "4"
tcpProto += "4"
} else if strings.HasSuffix(Config.General.Proto, "6") {
udpProto += "6"
tcpProto += "6"
}
dnsServerUDP := NewDNSServer(DB, Config.General.Listen, udpProto, Config.General.Domain)
dnsservers = append(dnsservers, dnsServerUDP)
dnsServerUDP.ParseRecords(Config)
dnsServerTCP := NewDNSServer(DB, Config.General.Listen, tcpProto, Config.General.Domain)
dnsservers = append(dnsservers, dnsServerTCP)
// No need to parse records from config again
dnsServerTCP.Domains = dnsServerUDP.Domains
dnsServerTCP.SOA = dnsServerUDP.SOA
go dnsServerUDP.Start(errChan)
go dnsServerTCP.Start(errChan)
} else {
dnsServer := NewDNSServer(DB, Config.General.Listen, Config.General.Proto, Config.General.Domain)
dnsservers = append(dnsservers, dnsServer)
dnsServer.ParseRecords(Config)
go dnsServer.Start(errChan)
}
// HTTP API
go startHTTPAPI(errChan, Config, dnsservers)
// block waiting for error
for {
err = <-errChan
if err != nil {
log.Fatal(err)
}
}
}
func startHTTPAPI(errChan chan error, config DNSConfig, dnsservers []*DNSServer) {
var err error
// Setup http logger
logger := log.New()
logwriter := logger.Writer()
defer logwriter.Close()
// Setup logging for different dependencies to log with logrus
// Certmagic
stdlog.SetOutput(logwriter)
// Lego
legolog.Logger = logger
api := httprouter.New()
c := cors.New(cors.Options{
AllowedOrigins: config.API.CorsOrigins,
AllowedMethods: []string{"GET", "POST"},
OptionsPassthrough: false,
Debug: config.General.Debug,
})
if config.General.Debug {
// Logwriter for saner log output
c.Log = stdlog.New(logwriter, "", 0)
}
if !config.API.DisableRegistration {
api.POST("/register", webRegisterPost)
}
api.POST("/update", Auth(webUpdatePost))
api.GET("/health", healthCheck)
host := config.API.IP + ":" + config.API.Port
// TLS specific general settings
cfg := &tls.Config{
MinVersion: tls.VersionTLS12,
}
provider := NewChallengeProvider(dnsservers)
storage := certmagic.FileStorage{Path: config.API.ACMECacheDir}
// Set up certmagic for getting certificate for acme-dns api
switch config.API.TLS {
case TlsTypeLetsEncrypt:
certmagic.DefaultACME.CA = certmagic.LetsEncryptProductionCA
case TlsTypeAcmeCustom:
certmagic.DefaultACME.CA = config.API.ACMEDir
case TlsTypeLetsEncryptStaging:
certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA
default:
}
certmagic.DefaultACME.Email = config.API.ACMENotificationEmail
certmagic.DefaultACME.Agreed = true
certmagic.DefaultACME.DNS01Solver = &provider
certmagic.Default.Storage = &storage
certmagic.Default.DefaultServerName = config.General.Domain
magic := certmagic.NewDefault()
srv := &http.Server{
Addr: host,
Handler: c.Handler(api),
ErrorLog: stdlog.New(logwriter, "", 0),
}
switch config.API.TLS {
case TlsTypeLetsEncrypt:
fallthrough
case TlsTypeLetsEncryptStaging:
fallthrough
case TlsTypeAcmeCustom:
err = magic.ManageAsync(context.Background(), []string{config.General.Domain})
if err != nil {
errChan <- err
return
}
cfg.GetCertificate = magic.GetCertificate
srv.TLSConfig = cfg
log.WithFields(log.Fields{"host": host, "domain": config.General.Domain}).Info("Listening HTTPS")
err = listenAndServe(srv, true, config)
case TlsTypeCert:
cfg.Certificates = make([]tls.Certificate, 1)
cfg.Certificates[0], err = tls.LoadX509KeyPair(config.API.TLSCertFullchain, config.API.TLSCertPrivkey)
if err != nil {
break
}
srv.TLSConfig = cfg
log.WithFields(log.Fields{"host": host}).Info("Listening HTTPS")
err = listenAndServe(srv, true, config)
case TlsTypeNone:
fallthrough
default:
log.WithFields(log.Fields{"host": host}).Info("Listening HTTP")
err = listenAndServe(srv, false, config)
}
if err != nil {
errChan <- err
}
}
func makePolicyFunc(trustedAddrs []string) (proxyproto.PolicyFunc, error) {
var err error
addrMatchers := make([]IpAddrMatcher, len(trustedAddrs))
for i, addr := range trustedAddrs {
log.WithField("addr", addr).Debug("Adding trusted proxy address")
addrMatchers[i], err = NewIpAddrMatcher(addr)
if err != nil {
log.WithField("value", addr).Errorf("Invalid trusted proxy address: %#v", addr)
return nil, err
}
}
return func(upstream net.Addr) (proxyproto.Policy, error) {
ip, err := ipFromAddr(upstream)
if err != nil {
return proxyproto.REJECT, err
}
for _, matcher := range addrMatchers {
if matcher.Contains(ip) {
return proxyproto.USE, nil
}
}
return proxyproto.IGNORE, nil
}, nil
}
func listenAndServe(srv *http.Server, tls bool, config DNSConfig) error {
listener, err := net.Listen("tcp", srv.Addr)
if err != nil {
return err
}
if config.API.Proxy {
log.Info("Listening with PROXY support")
pl := &proxyproto.Listener{Listener: listener}
if config.API.ProxyTrustedAddrs != nil && len(config.API.ProxyTrustedAddrs) >= 1 {
pl.Policy, err = makePolicyFunc(config.API.ProxyTrustedAddrs)
if err != nil {
return err
}
}
listener = pl
}
defer listener.Close()
if tls {
return srv.ServeTLS(listener, "", "")
} else {
return srv.Serve(listener)
}
}