Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 14, 05:15 UTC
acme_server: fix reload of acme database (#3874)
* acme_server: Refactor database creation apart from authority creation This is a WIP commit that doesn't really offer anything other than setting us up for using a UsagePool to gracefully reload acme_server configs. * Implement UsagePool * Remove unused context * Fix initializing non-ACME CA This will handle cases where a DB is not provided * Sanitize acme db path and clean debug logs * Move regex to package level to prevent recompiling
Ian committed
Nov 23, 2020 at 12:58 UTC
c5197f59991df7fbe6b921a2a84483d108525d03
2 files changed
+71
-27
modules/caddypki/acmeserver/acmeserver.go
+63
-23
@@ -19,6 +19,7 @@ import (
19
"net/http"
20
"os"
21
"path/filepath"
22
+ "regexp"
23
"strings"
24
"time"
25
@@ -66,6 +67,7 @@ type Handler struct {
67
PathPrefix string `json:"path_prefix,omitempty"`
68
69
acmeEndpoints http.Handler
70
+ logger *zap.Logger
71
}
72
73
// CaddyModule returns the Caddy module information.
@@ -78,7 +80,7 @@ func (Handler) CaddyModule() caddy.ModuleInfo {
80
81
// Provision sets up the ACME server handler.
82
func (ash *Handler) Provision(ctx caddy.Context) error {
81
- logger := ctx.Logger(ash)
83
+ ash.logger = ctx.Logger(ash)
84
// set some defaults
85
if ash.CA == "" {
86
ash.CA = caddypki.DefaultCAID
@@ -101,25 +103,9 @@ func (ash *Handler) Provision(ctx caddy.Context) error {
103
return fmt.Errorf("no certificate authority configured with id: %s", ash.CA)
104
}
105
104
- dbFolder := filepath.Join(caddy.AppDataDir(), "acme_server")
105
- dbPath := filepath.Join(dbFolder, "db")
106
-
107
- // TODO: See https://github.com/smallstep/nosql/issues/7
108
- err = os.MkdirAll(dbFolder, 0755)
106
+ database, err := ash.openDatabase()
107
if err != nil {
110
- return fmt.Errorf("making folder for ACME server database: %v", err)
111
- }
112
-
113
- // Check to see if previous db exists
114
- var stat os.FileInfo
115
- stat, err = os.Stat(dbPath)
116
- if stat != nil && err == nil {
117
- // A badger db is found and should be removed
118
- if stat.IsDir() {
119
- logger.Warn("Found an old badger database and removing it",
120
- zap.String("path", dbPath))
121
- _ = os.RemoveAll(dbPath)
122
- }
108
+ return err
109
}
110
111
authorityConfig := caddypki.AuthorityConfig{
@@ -136,10 +122,7 @@ func (ash *Handler) Provision(ctx caddy.Context) error {
122
},
123
},
124
},
139
- DB: &db.Config{
140
- Type: "bbolt",
141
- DataSource: dbPath,
142
- },
125
+ DB: database,
126
}
127
128
auth, err := ca.NewAuthority(authorityConfig)
@@ -175,11 +158,68 @@ func (ash Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyh
158
return next.ServeHTTP(w, r)
159
}
160
161
+func (ash Handler) getDatabaseKey() string {
162
+ key := ash.CA
163
+ key = strings.ToLower(key)
164
+ key = strings.TrimSpace(key)
165
+ return keyCleaner.ReplaceAllLiteralString(key, "")
166
+}
167
+
168
+// Cleanup implements caddy.CleanerUpper and closes any idle databases.
169
+func (ash Handler) Cleanup() error {
170
+ key := ash.getDatabaseKey()
171
+ deleted, err := databasePool.Delete(key)
172
+ if deleted {
173
+ ash.logger.Debug("unloading unused CA database", zap.String("db_key", key))
174
+ }
175
+ if err != nil {
176
+ ash.logger.Error("closing CA database", zap.String("db_key", key), zap.Error(err))
177
+ }
178
+ return err
179
+}
180
+
181
+func (ash Handler) openDatabase() (*db.AuthDB, error) {
182
+ key := ash.getDatabaseKey()
183
+ database, loaded, err := databasePool.LoadOrNew(key, func() (caddy.Destructor, error) {
184
+ dbFolder := filepath.Join(caddy.AppDataDir(), "acme_server", key)
185
+ dbPath := filepath.Join(dbFolder, "db")
186
+
187
+ err := os.MkdirAll(dbFolder, 0755)
188
+ if err != nil {
189
+ return nil, fmt.Errorf("making folder for CA database: %v", err)
190
+ }
191
+
192
+ dbConfig := &db.Config{
193
+ Type: "bbolt",
194
+ DataSource: dbPath,
195
+ }
196
+ database, err := db.New(dbConfig)
197
+ return databaseCloser{&database}, err
198
+ })
199
+
200
+ if loaded {
201
+ ash.logger.Debug("loaded preexisting CA database", zap.String("db_key", key))
202
+ }
203
+
204
+ return database.(databaseCloser).DB, err
205
+}
206
+
207
const (
208
defaultHost = "localhost"
209
defaultPathPrefix = "/acme/"
210
)
211
212
+var keyCleaner = regexp.MustCompile(`[^\w.-_]`)
213
+var databasePool = caddy.NewUsagePool()
214
+
215
+type databaseCloser struct {
216
+ DB *db.AuthDB
217
+}
218
+
219
+func (closer databaseCloser) Destruct() error {
220
+ return (*closer.DB).Shutdown()
221
+}
222
+
223
// Interface guards
224
var (
225
_ caddyhttp.MiddlewareHandler = (*Handler)(nil)
modules/caddypki/ca.go
+8
-4
@@ -195,14 +195,18 @@ func (ca CA) NewAuthority(authorityConfig AuthorityConfig) (*authority.Authority
195
issuerKey = ca.IntermediateKey()
196
}
197
198
- auth, err := authority.NewEmbedded(
198
+ opts := []authority.Option{
199
authority.WithConfig(&authority.Config{
200
AuthorityConfig: authorityConfig.AuthConfig,
201
- DB: authorityConfig.DB,
201
}),
202
authority.WithX509Signer(issuerCert, issuerKey.(crypto.Signer)),
203
authority.WithX509RootCerts(rootCert),
205
- )
204
+ }
205
+ // Add a database if we have one
206
+ if authorityConfig.DB != nil {
207
+ opts = append(opts, authority.WithDatabase(*authorityConfig.DB))
208
+ }
209
+ auth, err := authority.NewEmbedded(opts...)
210
if err != nil {
211
return nil, fmt.Errorf("initializing certificate authority: %v", err)
212
}
@@ -382,7 +386,7 @@ type AuthorityConfig struct {
386
SignWithRoot bool
387
388
// TODO: should we just embed the underlying authority.Config struct type?
385
- DB *db.Config
389
+ DB *db.AuthDB
390
AuthConfig *authority.AuthConfig
391
}
392