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
Initial commit of Storage, TLS, and automatic HTTPS implementations
Matthew Holt committed
Apr 25, 2019 at 13:54 UTC
2d056fbe66849f041a233a0d961639fae3835cbb
16 files changed
+1278
-118
admin.go
+3
-3
@@ -119,15 +119,15 @@ func Load(r io.Reader) error {
119
return err
120
}
121
122
- var cfg Config
122
+ var cfg *Config
123
err = json.Unmarshal(buf.Bytes(), &cfg)
124
if err != nil {
125
return fmt.Errorf("decoding config: %v", err)
126
}
127
128
- err = Start(cfg)
128
+ err = Run(cfg)
129
if err != nil {
130
- return fmt.Errorf("starting: %v", err)
130
+ return fmt.Errorf("running: %v", err)
131
}
132
133
return nil
caddy.go
+184
-73
@@ -8,27 +8,36 @@ import (
8
"sync"
9
"sync/atomic"
10
"time"
11
+
12
+ "github.com/mholt/certmagic"
13
)
14
13
-// Start runs Caddy with the given config.
14
-func Start(cfg Config) error {
15
+// Run runs Caddy with the given config.
16
+func Run(cfg *Config) error {
17
// allow only one call to Start at a time,
18
// since various calls to LoadModule()
19
// access shared map moduleInstances
20
startMu.Lock()
21
defer startMu.Unlock()
22
21
- // prepare the config for use
22
- cfg.runners = make(map[string]Runner)
23
+ // because we will need to roll back any state
24
+ // modifications if this function errors, we
25
+ // keep a single error value and scope all
26
+ // sub-operations to their own functions to
27
+ // ensure this error value does not get
28
+ // overridden or missed when it should have
29
+ // been set by a short assignment
30
+ var err error
31
+
32
+ // prepare the new config for use
33
+ cfg.apps = make(map[string]App)
34
cfg.moduleStates = make(map[string]interface{})
35
36
// reset the shared moduleInstances map; but
37
// keep a temporary reference to the current
38
// one so we can transfer over any necessary
28
- // state to the new modules; or in case this
29
- // function returns an error, we need to put
30
- // the "old" one back where we found it
31
- var err error
39
+ // state to the new modules or to roll back
40
+ // if necessary
41
oldModuleInstances := moduleInstances
42
defer func() {
43
if err != nil {
@@ -37,109 +46,183 @@ func Start(cfg Config) error {
46
}()
47
moduleInstances = make(map[string][]interface{})
48
40
- // load (decode) each runner module
41
- for modName, rawMsg := range cfg.Modules {
42
- val, err := LoadModule(modName, rawMsg)
43
- if err != nil {
44
- return fmt.Errorf("loading module '%s': %v", modName, err)
49
+ // set up storage and make it CertMagic's default storage, too
50
+ err = func() error {
51
+ if cfg.StorageRaw != nil {
52
+ val, err := LoadModuleInline("system", "caddy.storage", cfg.StorageRaw)
53
+ if err != nil {
54
+ return fmt.Errorf("loading storage module: %v", err)
55
+ }
56
+ stor, err := val.(StorageConverter).CertMagicStorage()
57
+ if err != nil {
58
+ return fmt.Errorf("creating storage value: %v", err)
59
+ }
60
+ cfg.storage = stor
61
+ cfg.StorageRaw = nil // allow GC to deallocate - TODO: Does this help?
62
}
46
- cfg.runners[modName] = val.(Runner)
47
- }
48
-
49
- // start the new runners
50
- for name, r := range cfg.runners {
51
- err := r.Run()
52
- if err != nil {
53
- // TODO: If any one has an error, stop the others
54
- return fmt.Errorf("%s module: %v", name, err)
63
+ if cfg.storage == nil {
64
+ cfg.storage = &certmagic.FileStorage{Path: dataDir()}
65
}
66
+ certmagic.Default.Storage = cfg.storage
67
+
68
+ return nil
69
+ }()
70
+ if err != nil {
71
+ return err
72
}
73
58
- // shut down down the old runners
59
- currentCfgMu.Lock()
60
- if currentCfg != nil {
61
- for name, r := range currentCfg.runners {
62
- err := r.Cancel()
74
+ // Load, Provision, Validate
75
+ err = func() error {
76
+ for modName, rawMsg := range cfg.AppsRaw {
77
+ val, err := LoadModule(modName, rawMsg)
78
if err != nil {
64
- log.Printf("[ERROR] cancel %s: %v", name, err)
79
+ return fmt.Errorf("loading app module '%s': %v", modName, err)
80
}
81
+ cfg.apps[modName] = val.(App)
82
}
83
+ return nil
84
+ }()
85
+ if err != nil {
86
+ return err
87
}
88
+
89
+ // swap old config with the new one, and
90
+ // roll back this change if anything fails
91
+ currentCfgMu.Lock()
92
oldCfg := currentCfg
69
- currentCfg = &cfg
93
+ currentCfg = cfg
94
currentCfgMu.Unlock()
71
-
72
- // invoke unload callbacks on old configuration
73
- for modName := range oldModuleInstances {
74
- mod, err := GetModule(modName)
95
+ defer func() {
96
if err != nil {
76
- return err
97
+ currentCfgMu.Lock()
98
+ currentCfg = oldCfg
99
+ currentCfgMu.Unlock()
100
}
78
- if mod.OnUnload != nil {
79
- var unloadingState interface{}
80
- if oldCfg != nil {
81
- unloadingState = oldCfg.moduleStates[modName]
82
- }
83
- err := mod.OnUnload(unloadingState)
101
+ }()
102
+
103
+ // OnLoad
104
+ err = func() error {
105
+ for modName, instances := range moduleInstances {
106
+ mod, err := GetModule(modName)
107
if err != nil {
85
- log.Printf("[ERROR] module OnUnload: %s: %v", modName, err)
86
- continue
108
+ return err
109
+ }
110
+ if mod.OnLoad != nil {
111
+ var priorState interface{}
112
+ if oldCfg != nil {
113
+ priorState = oldCfg.moduleStates[modName]
114
+ }
115
+ modState, err := mod.OnLoad(instances, priorState)
116
+ if err != nil {
117
+ return fmt.Errorf("module OnLoad: %s: %v", modName, err)
118
+ }
119
+ if modState != nil {
120
+ cfg.moduleStates[modName] = modState
121
+ }
122
}
123
}
124
+ return nil
125
+ }()
126
+ if err != nil {
127
+ return err
128
}
129
91
- // invoke load callbacks on new configuration
92
- for modName, instances := range moduleInstances {
93
- mod, err := GetModule(modName)
94
- if err != nil {
95
- return err
130
+ // Start
131
+ err = func() error {
132
+ h := Handle{cfg}
133
+ for name, a := range cfg.apps {
134
+ err := a.Start(h)
135
+ if err != nil {
136
+ for otherAppName, otherApp := range cfg.apps {
137
+ err := otherApp.Stop()
138
+ if err != nil {
139
+ log.Printf("aborting app %s: %v", otherAppName, err)
140
+ }
141
+ }
142
+ return fmt.Errorf("%s app module: start: %v", name, err)
143
+ }
144
}
97
- if mod.OnLoad != nil {
98
- var priorState interface{}
99
- if oldCfg != nil {
100
- priorState = oldCfg.moduleStates[modName]
145
+ return nil
146
+ }()
147
+ if err != nil {
148
+ return err
149
+ }
150
+
151
+ // Stop
152
+ if oldCfg != nil {
153
+ for name, a := range oldCfg.apps {
154
+ err := a.Stop()
155
+ if err != nil {
156
+ log.Printf("[ERROR] stop %s: %v", name, err)
157
}
102
- modState, err := mod.OnLoad(instances, priorState)
158
+ }
159
+ }
160
+
161
+ // OnUnload
162
+ err = func() error {
163
+ for modName := range oldModuleInstances {
164
+ mod, err := GetModule(modName)
165
if err != nil {
104
- return fmt.Errorf("module OnLoad: %s: %v", modName, err)
166
+ return err
167
}
106
- if modState != nil {
107
- cfg.moduleStates[modName] = modState
168
+ if mod.OnUnload != nil {
169
+ var unloadingState interface{}
170
+ if oldCfg != nil {
171
+ unloadingState = oldCfg.moduleStates[modName]
172
+ }
173
+ err := mod.OnUnload(unloadingState)
174
+ if err != nil {
175
+ log.Printf("[ERROR] module OnUnload: %s: %v", modName, err)
176
+ continue
177
+ }
178
}
179
}
180
+ return nil
181
+ }()
182
+ if err != nil {
183
+ return err
184
}
185
186
// shut down listeners that are no longer being used
113
- listenersMu.Lock()
114
- for key, info := range listeners {
115
- if atomic.LoadInt32(&info.usage) == 0 {
116
- err := info.ln.Close()
117
- if err != nil {
118
- log.Printf("[ERROR] closing listener %s: %v", info.ln.Addr(), err)
119
- continue
187
+ err = func() error {
188
+ listenersMu.Lock()
189
+ for key, info := range listeners {
190
+ if atomic.LoadInt32(&info.usage) == 0 {
191
+ err := info.ln.Close()
192
+ if err != nil {
193
+ log.Printf("[ERROR] closing listener %s: %v", info.ln.Addr(), err)
194
+ continue
195
+ }
196
+ delete(listeners, key)
197
}
121
- delete(listeners, key)
198
}
199
+ listenersMu.Unlock()
200
+ return nil
201
+ }()
202
+ if err != nil {
203
+ return err
204
}
124
- listenersMu.Unlock()
205
206
return nil
207
}
208
129
-// Runner is a thing that Caddy runs.
130
-type Runner interface {
131
- Run() error
132
- Cancel() error
209
+// App is a thing that Caddy runs.
210
+type App interface {
211
+ Start(Handle) error
212
+ Stop() error
213
}
214
215
// Config represents a Caddy configuration.
216
type Config struct {
217
+ StorageRaw json.RawMessage `json:"storage"`
218
+ storage certmagic.Storage
219
+
220
TestVal string `json:"testval"`
138
- Modules map[string]json.RawMessage `json:"modules"`
221
+ AppsRaw map[string]json.RawMessage `json:"apps"`
222
140
- // runners stores the decoded Modules values,
223
+ // apps stores the decoded Apps values,
224
// keyed by module name.
142
- runners map[string]Runner
225
+ apps map[string]App
226
227
// moduleStates stores the optional "global" state
228
// values of every module used by this configuration,
@@ -147,6 +230,34 @@ type Config struct {
230
moduleStates map[string]interface{}
231
}
232
233
+// Handle allows app modules to access
234
+// the top-level Config in a controlled
235
+// manner without needing to rely on
236
+// global state.
237
+type Handle struct {
238
+ current *Config
239
+}
240
+
241
+// App returns the configured app named name.
242
+// A nil value is returned if no app with that
243
+// name is currently configured.
244
+func (h Handle) App(name string) interface{} {
245
+ return h.current.apps[name]
246
+}
247
+
248
+// GetStorage returns the configured Caddy storage implementation.
249
+// If no storage implementation is explicitly configured, the
250
+// default one is returned instead, as long as there is a current
251
+// configuration loaded.
252
+func GetStorage() certmagic.Storage {
253
+ currentCfgMu.RLock()
254
+ defer currentCfgMu.RUnlock()
255
+ if currentCfg == nil {
256
+ return nil
257
+ }
258
+ return currentCfg.storage
259
+}
260
+
261
// Duration is a JSON-string-unmarshable duration type.
262
type Duration time.Duration
263
@@ -167,7 +278,7 @@ type CtxKey string
278
// currentCfg is the currently-loaded configuration.
279
var (
280
currentCfg *Config
170
- currentCfgMu sync.Mutex
281
+ currentCfgMu sync.RWMutex
282
)
283
284
// moduleInstances stores the individual instantiated
@@ -181,5 +292,5 @@ var (
292
var moduleInstances = make(map[string][]interface{})
293
294
// startMu ensures that only one Start() happens at a time.
184
-// This is important since
295
+// This is important since moduleInstances is shared state.
296
var startMu sync.Mutex
cmd/caddy2/main.go
+1
@@ -12,6 +12,7 @@ import (
12
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp"
13
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/caddylog"
14
_ "bitbucket.org/lightcodelabs/caddy2/modules/caddyhttp/staticfiles"
15
+ _ "bitbucket.org/lightcodelabs/caddy2/modules/caddytls"
16
_ "bitbucket.org/lightcodelabs/dynamicconfig"
17
_ "bitbucket.org/lightcodelabs/proxy"
18
)
listeners.go
+2
-2
@@ -111,7 +111,7 @@ func (fcl *fakeCloseListener) fakeClosedErr() error {
111
Op: "accept",
112
Net: fcl.Listener.Addr().Network(),
113
Addr: fcl.Listener.Addr(),
114
- Err: ErrFakeClosed,
114
+ Err: errFakeClosed,
115
}
116
}
117
@@ -120,7 +120,7 @@ func (fcl *fakeCloseListener) fakeClosedErr() error {
120
// indicating that it is pretending to be closed so that the
121
// server using it can terminate, while the underlying
122
// socket is actually left open.
123
-var ErrFakeClosed = fmt.Errorf("listener 'closed' 😉")
123
+var errFakeClosed = fmt.Errorf("listener 'closed' 😉")
124
125
// listenerUsage pairs a net.Listener with a
126
// count of how many servers are using it.
modules.go
+50
-16
@@ -9,7 +9,7 @@ import (
9
"sync"
10
)
11
12
-// Module is a module.
12
+// Module represents a Caddy module.
13
type Module struct {
14
Name string
15
New func() (interface{}, error)
@@ -21,6 +21,10 @@ func (m Module) String() string { return m.Name }
21
22
// RegisterModule registers a module.
23
func RegisterModule(mod Module) error {
24
+ if mod.Name == "caddy" {
25
+ return fmt.Errorf("modules cannot be named 'caddy'")
26
+ }
27
+
28
modulesMu.Lock()
29
defer modulesMu.Unlock()
30
@@ -45,7 +49,7 @@ func GetModule(name string) (Module, error) {
49
50
// GetModules returns all modules in the given scope/namespace.
51
// For example, a scope of "foo" returns modules named "foo.bar",
48
-// "foo.lee", but not "bar", "foo.bar.lee", etc. An empty scope
52
+// "foo.loo", but not "bar", "foo.bar.loo", etc. An empty scope
53
// returns top-level modules, for example "foo" or "bar". Partial
54
// scopes are not matched (i.e. scope "foo.ba" does not match
55
// name "foo.bar").
@@ -112,7 +116,10 @@ func Modules() []string {
116
// returns the value. If mod.New() does not return a pointer
117
// value, it is converted to one so that it is unmarshaled
118
// into the underlying concrete type. If mod.New is nil, an
115
-// error is returned.
119
+// error is returned. If the module implements Validator or
120
+// Provisioner interfaces, those methods are invoked to
121
+// ensure the module is fully configured and valid before
122
+// being used.
123
func LoadModule(name string, rawMsg json.RawMessage) (interface{}, error) {
124
modulesMu.Lock()
125
mod, ok := modules[name]
@@ -140,6 +147,13 @@ func LoadModule(name string, rawMsg json.RawMessage) (interface{}, error) {
147
return nil, fmt.Errorf("decoding module config: %s: %v", mod.Name, err)
148
}
149
150
+ if prov, ok := val.(Provisioner); ok {
151
+ err := prov.Provision()
152
+ if err != nil {
153
+ return nil, fmt.Errorf("provision %s: %v", mod.Name, err)
154
+ }
155
+ }
156
+
157
if validator, ok := val.(Validator); ok {
158
err := validator.Validate()
159
if err != nil {
@@ -152,27 +166,23 @@ func LoadModule(name string, rawMsg json.RawMessage) (interface{}, error) {
166
return val, nil
167
}
168
155
-// LoadModuleInlineName loads a module from a JSON raw message which
156
-// decodes to a map[string]interface{}, and where one of the keys is
157
-// "_module", which indicates the module name and which be found in
158
-// the given scope.
169
+// LoadModuleInline loads a module from a JSON raw message which decodes
170
+// to a map[string]interface{}, where one of the keys is moduleNameKey
171
+// and the corresponding value is the module name as a string, which
172
+// can be found in the given scope.
173
//
174
// This allows modules to be decoded into their concrete types and
175
// used when their names cannot be the unique key in a map, such as
176
// when there are multiple instances in the map or it appears in an
163
-// array (where there are no custom keys).
164
-func LoadModuleInlineName(moduleScope string, raw json.RawMessage) (interface{}, error) {
165
- var tmp map[string]interface{}
166
- err := json.Unmarshal(raw, &tmp)
177
+// array (where there are no custom keys). In other words, the key
178
+// containing the module name is treated special/separate from all
179
+// the other keys.
180
+func LoadModuleInline(moduleNameKey, moduleScope string, raw json.RawMessage) (interface{}, error) {
181
+ moduleName, err := getModuleNameInline(moduleNameKey, raw)
182
if err != nil {
183
return nil, err
184
}
185
171
- moduleName, ok := tmp["_module"].(string)
172
- if !ok || moduleName == "" {
173
- return nil, fmt.Errorf("module name not specified")
174
- }
175
-
186
val, err := LoadModule(moduleScope+"."+moduleName, raw)
187
if err != nil {
188
return nil, fmt.Errorf("loading module '%s': %v", moduleName, err)
@@ -181,6 +191,23 @@ func LoadModuleInlineName(moduleScope string, raw json.RawMessage) (interface{},
191
return val, nil
192
}
193
194
+// getModuleNameInline loads the string value from raw of moduleNameKey,
195
+// where raw must be a JSON encoding of a map.
196
+func getModuleNameInline(moduleNameKey string, raw json.RawMessage) (string, error) {
197
+ var tmp map[string]interface{}
198
+ err := json.Unmarshal(raw, &tmp)
199
+ if err != nil {
200
+ return "", err
201
+ }
202
+
203
+ moduleName, ok := tmp[moduleNameKey].(string)
204
+ if !ok || moduleName == "" {
205
+ return "", fmt.Errorf("module name not specified with key '%s' in %+v", moduleNameKey, tmp)
206
+ }
207
+
208
+ return moduleName, nil
209
+}
210
+
211
// Validator is implemented by modules which can verify that their
212
// configurations are valid. This method will be called after New()
213
// instantiations of modules (if implemented). Validation should
@@ -190,6 +217,13 @@ type Validator interface {
217
Validate() error
218
}
219
220
+// Provisioner is implemented by modules which may need to perform
221
+// some additional "setup" steps immediately after being loaded.
222
+// This method will be called after Validate() (if implemented).
223
+type Provisioner interface {
224
+ Provision() error
225
+}
226
+
227
var (
228
modules = make(map[string]Module)
229
modulesMu sync.Mutex
modules/caddyhttp/caddyhttp.go
+97
-12
@@ -2,6 +2,7 @@ package caddyhttp
2
3
import (
4
"context"
5
+ "crypto/tls"
6
"fmt"
7
"log"
8
mathrand "math/rand"
@@ -12,9 +13,13 @@ import (
13
"time"
14
15
"bitbucket.org/lightcodelabs/caddy2"
16
+ "bitbucket.org/lightcodelabs/caddy2/modules/caddytls"
17
+ "github.com/mholt/certmagic"
18
)
19
20
func init() {
21
+ mathrand.Seed(time.Now().UnixNano())
22
+
23
err := caddy2.RegisterModule(caddy2.Module{
24
Name: "http",
25
New: func() (interface{}, error) { return new(httpModuleConfig), nil },
@@ -22,17 +27,15 @@ func init() {
27
if err != nil {
28
log.Fatal(err)
29
}
25
-
26
- mathrand.Seed(time.Now().UnixNano())
30
}
31
32
type httpModuleConfig struct {
30
- Servers map[string]httpServerConfig `json:"servers"`
33
+ Servers map[string]*httpServerConfig `json:"servers"`
34
35
servers []*http.Server
36
}
37
35
-func (hc *httpModuleConfig) Run() error {
38
+func (hc *httpModuleConfig) Provision() error {
39
// TODO: Either prevent overlapping listeners on different servers, or combine them into one
40
for _, srv := range hc.Servers {
41
err := srv.Routes.setup()
@@ -43,7 +46,18 @@ func (hc *httpModuleConfig) Run() error {
46
if err != nil {
47
return fmt.Errorf("setting up server error handling routes: %v", err)
48
}
49
+ }
50
51
+ return nil
52
+}
53
+
54
+func (hc *httpModuleConfig) Start(handle caddy2.Handle) error {
55
+ err := hc.automaticHTTPS(handle)
56
+ if err != nil {
57
+ return fmt.Errorf("enabling automatic HTTPS: %v", err)
58
+ }
59
+
60
+ for srvName, srv := range hc.Servers {
61
s := &http.Server{
62
ReadTimeout: time.Duration(srv.ReadTimeout),
63
ReadHeaderTimeout: time.Duration(srv.ReadHeaderTimeout),
@@ -53,13 +67,30 @@ func (hc *httpModuleConfig) Run() error {
67
for _, lnAddr := range srv.Listen {
68
network, addrs, err := parseListenAddr(lnAddr)
69
if err != nil {
56
- return fmt.Errorf("parsing listen address '%s': %v", lnAddr, err)
70
+ return fmt.Errorf("%s: parsing listen address '%s': %v", srvName, lnAddr, err)
71
}
72
for _, addr := range addrs {
73
ln, err := caddy2.Listen(network, addr)
74
if err != nil {
75
return fmt.Errorf("%s: listening on %s: %v", network, addr, err)
76
}
77
+
78
+ // enable HTTP/2 by default
79
+ for _, pol := range srv.TLSConnPolicies {
80
+ if len(pol.ALPN) == 0 {
81
+ pol.ALPN = append(pol.ALPN, defaultALPN...)
82
+ }
83
+ }
84
+
85
+ // enable TLS
86
+ if len(srv.TLSConnPolicies) > 0 {
87
+ tlsCfg, err := srv.TLSConnPolicies.TLSConfig(handle)
88
+ if err != nil {
89
+ return fmt.Errorf("%s/%s: making TLS configuration: %v", network, addr, err)
90
+ }
91
+ ln = tls.NewListener(ln, tlsCfg)
92
+ }
93
+
94
go s.Serve(ln)
95
hc.servers = append(hc.servers, s)
96
}
@@ -69,7 +100,7 @@ func (hc *httpModuleConfig) Run() error {
100
return nil
101
}
102
72
-func (hc *httpModuleConfig) Cancel() error {
103
+func (hc *httpModuleConfig) Stop() error {
104
for _, s := range hc.servers {
105
err := s.Shutdown(context.Background()) // TODO
106
if err != nil {
@@ -79,13 +110,63 @@ func (hc *httpModuleConfig) Cancel() error {
110
return nil
111
}
112
113
+func (hc *httpModuleConfig) automaticHTTPS(handle caddy2.Handle) error {
114
+ tlsApp := handle.App("tls").(*caddytls.TLS)
115
+
116
+ for srvName, srv := range hc.Servers {
117
+ srv.tlsApp = tlsApp
118
+
119
+ if srv.DisableAutoHTTPS {
120
+ continue
121
+ }
122
+
123
+ domainSet := make(map[string]struct{})
124
+ for _, route := range srv.Routes {
125
+ for _, m := range route.matchers {
126
+ if hm, ok := m.(*matchHost); ok {
127
+ for _, d := range *hm {
128
+ if !certmagic.HostQualifies(d) {
129
+ continue
130
+ }
131
+ domainSet[d] = struct{}{}
132
+ }
133
+ }
134
+ }
135
+ }
136
+ var domains []string
137
+ for d := range domainSet {
138
+ domains = append(domains, d)
139
+ }
140
+ if len(domains) > 0 {
141
+ err := tlsApp.Manage(domains)
142
+ if err != nil {
143
+ return fmt.Errorf("%s: managing certificate for %s: %s", srvName, domains, err)
144
+ }
145
+ // TODO: Connection policies... redirects... man...
146
+ srv.TLSConnPolicies = caddytls.ConnectionPolicies{
147
+ {
148
+ ALPN: defaultALPN,
149
+ },
150
+ }
151
+ }
152
+ }
153
+
154
+ return nil
155
+}
156
+
157
+var defaultALPN = []string{"h2", "http/1.1"}
158
+
159
type httpServerConfig struct {
83
- Listen []string `json:"listen"`
84
- ReadTimeout caddy2.Duration `json:"read_timeout"`
85
- ReadHeaderTimeout caddy2.Duration `json:"read_header_timeout"`
86
- HiddenFiles []string `json:"hidden_files"` // TODO:... experimenting with shared/common state
87
- Routes routeList `json:"routes"`
88
- Errors httpErrorConfig `json:"errors"`
160
+ Listen []string `json:"listen"`
161
+ ReadTimeout caddy2.Duration `json:"read_timeout"`
162
+ ReadHeaderTimeout caddy2.Duration `json:"read_header_timeout"`
163
+ HiddenFiles []string `json:"hidden_files"` // TODO:... experimenting with shared/common state
164
+ Routes routeList `json:"routes"`
165
+ Errors httpErrorConfig `json:"errors"`
166
+ TLSConnPolicies caddytls.ConnectionPolicies `json:"tls_connection_policies"`
167
+ DisableAutoHTTPS bool `json:"disable_auto_https"`
168
+
169
+ tlsApp *caddytls.TLS
170
}
171
172
type httpErrorConfig struct {
@@ -95,6 +176,10 @@ type httpErrorConfig struct {
176
177
// ServeHTTP is the entry point for all HTTP requests.
178
func (s httpServerConfig) ServeHTTP(w http.ResponseWriter, r *http.Request) {
179
+ if s.tlsApp.HandleHTTPChallenge(w, r) {
180
+ return
181
+ }
182
+
183
stack := s.Routes.buildMiddlewareChain(w, r)
184
err := executeMiddlewareChain(w, r, stack)
185
if err != nil {
modules/caddyhttp/caddylog/log.go
+1
-1
@@ -64,4 +64,4 @@ func (l *Log) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.H
64
}
65
66
// Interface guard
67
-var _ caddyhttp.MiddlewareHandler = &Log{}
67
+var _ caddyhttp.MiddlewareHandler = (*Log)(nil)
modules/caddyhttp/routes.go
+10
-9
@@ -32,17 +32,13 @@ func (routes routeList) buildMiddlewareChain(w http.ResponseWriter, r *http.Requ
32
var responder Handler
33
mrw := &middlewareResponseWriter{ResponseWriterWrapper: &ResponseWriterWrapper{w}}
34
35
+routeLoop:
36
for _, route := range routes {
36
- matched := len(route.matchers) == 0
37
for _, m := range route.matchers {
38
- if m.Match(r) {
39
- matched = true
40
- break
38
+ if !m.Match(r) {
39
+ continue routeLoop
40
}
41
}
43
- if !matched {
44
- continue
45
- }
42
for _, m := range route.middleware {
43
mid = append(mid, func(next HandlerFunc) HandlerFunc {
44
return func(w http.ResponseWriter, r *http.Request) error {
@@ -53,6 +49,8 @@ func (routes routeList) buildMiddlewareChain(w http.ResponseWriter, r *http.Requ
49
if responder == nil {
50
responder = route.responder
51
}
52
+ // TODO: Should exclusive apply to only middlewares, or responder too?
53
+ // i.e. what if they haven't set a responder yet, but the first middleware chain is exclusive...
54
if route.Exclusive {
55
break
56
}
@@ -83,24 +81,27 @@ func (routes routeList) setup() error {
81
}
82
routes[i].matchers = append(routes[i].matchers, val.(RouteMatcher))
83
}
84
+ routes[i].Matchers = nil // allow GC to deallocate - TODO: Does this help?
85
86
// middleware
87
for j, rawMsg := range route.Apply {
89
- mid, err := caddy2.LoadModuleInlineName("http.middleware", rawMsg)
88
+ mid, err := caddy2.LoadModuleInline("middleware", "http.middleware", rawMsg)
89
if err != nil {
90
return fmt.Errorf("loading middleware module in position %d: %v", j, err)
91
}
92
routes[i].middleware = append(routes[i].middleware, mid.(MiddlewareHandler))
93
}
94
+ routes[i].Apply = nil // allow GC to deallocate - TODO: Does this help?
95
96
// responder
97
if route.Respond != nil {
98
- resp, err := caddy2.LoadModuleInlineName("http.responders", route.Respond)
98
+ resp, err := caddy2.LoadModuleInline("responder", "http.responders", route.Respond)
99
if err != nil {
100
return fmt.Errorf("loading responder module: %v", err)
101
}
102
routes[i].responder = resp.(Handler)
103
}
104
+ routes[i].Respond = nil // allow GC to deallocate - TODO: Does this help?
105
}
106
return nil
107
}
modules/caddyhttp/staticfiles/staticfiles.go
+2
-2
@@ -10,7 +10,7 @@ import (
10
func init() {
11
caddy2.RegisterModule(caddy2.Module{
12
Name: "http.responders.static_files",
13
- New: func() (interface{}, error) { return &StaticFiles{}, nil },
13
+ New: func() (interface{}, error) { return new(StaticFiles), nil },
14
})
15
}
16
@@ -25,4 +25,4 @@ func (sf StaticFiles) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
25
}
26
27
// Interface guard
28
-var _ caddyhttp.Handler = StaticFiles{}
28
+var _ caddyhttp.Handler = (*StaticFiles)(nil)
modules/caddytls/acmemanager.go
new
+84
@@ -0,0 +1,84 @@
1
+package caddytls
2
+
3
+import (
4
+ "encoding/json"
5
+ "fmt"
6
+
7
+ "github.com/go-acme/lego/certcrypto"
8
+
9
+ "bitbucket.org/lightcodelabs/caddy2"
10
+ "github.com/go-acme/lego/challenge"
11
+ "github.com/mholt/certmagic"
12
+)
13
+
14
+func init() {
15
+ caddy2.RegisterModule(caddy2.Module{
16
+ Name: "tls.management.acme",
17
+ New: func() (interface{}, error) { return new(acmeManagerMaker), nil },
18
+ })
19
+}
20
+
21
+// ManagerMaker TODO: WIP...
22
+type ManagerMaker interface {
23
+ newManager(interactive bool) (certmagic.Manager, error)
24
+}
25
+
26
+// acmeManagerMaker makes an ACME manager
27
+// for managinig certificates using ACME.
28
+type acmeManagerMaker struct {
29
+ CA string `json:"ca,omitempty"`
30
+ Email string `json:"email,omitempty"`
31
+ RenewAhead caddy2.Duration `json:"renew_ahead,omitempty"`
32
+ KeyType string `json:"key_type,omitempty"`
33
+ ACMETimeout caddy2.Duration `json:"acme_timeout,omitempty"`
34
+ MustStaple bool `json:"must_staple,omitempty"`
35
+ Challenges ChallengesConfig `json:"challenges"`
36
+ OnDemand *OnDemandConfig `json:"on_demand,omitempty"`
37
+ Storage json.RawMessage `json:"storage,omitempty"`
38
+
39
+ storage certmagic.Storage
40
+ keyType certcrypto.KeyType
41
+}
42
+
43
+func (m *acmeManagerMaker) Provision() error {
44
+ m.setDefaults()
45
+
46
+ // DNS providers
47
+ if m.Challenges.DNS != nil {
48
+ val, err := caddy2.LoadModuleInline("provider", "tls.dns", m.Challenges.DNS)
49
+ if err != nil {
50
+ return fmt.Errorf("loading TLS storage module: %s", err)
51
+ }
52
+ m.Challenges.dns = val.(challenge.Provider)
53
+ m.Challenges.DNS = nil // allow GC to deallocate - TODO: Does this help?
54
+ }
55
+
56
+ // policy-specific storage implementation
57
+ if m.Storage != nil {
58
+ val, err := caddy2.LoadModuleInline("system", "caddy.storage", m.Storage)
59
+ if err != nil {
60
+ return fmt.Errorf("loading TLS storage module: %s", err)
61
+ }
62
+ cmStorage, err := val.(caddy2.StorageConverter).CertMagicStorage()
63
+ if err != nil {
64
+ return fmt.Errorf("creating TLS storage configuration: %v", err)
65
+ }
66
+ m.storage = cmStorage
67
+ m.Storage = nil // allow GC to deallocate - TODO: Does this help?
68
+ }
69
+
70
+ return nil
71
+}
72
+
73
+// setDefaults indiscriminately sets all the default values in m.
74
+func (m *acmeManagerMaker) setDefaults() {
75
+ m.CA = certmagic.LetsEncryptStagingCA // certmagic.Default.CA // TODO: When not testing, switch to production CA
76
+ m.Email = certmagic.Default.Email
77
+ m.RenewAhead = caddy2.Duration(certmagic.Default.RenewDurationBefore)
78
+ m.keyType = certmagic.Default.KeyType
79
+ m.storage = certmagic.Default.Storage
80
+}
81
+
82
+func (m *acmeManagerMaker) newManager(interactive bool) (certmagic.Manager, error) {
83
+ return nil, nil
84
+}
modules/caddytls/connpolicy.go
new
+149
@@ -0,0 +1,149 @@
1
+package caddytls
2
+
3
+import (
4
+ "crypto/tls"
5
+ "encoding/json"
6
+ "fmt"
7
+
8
+ "bitbucket.org/lightcodelabs/caddy2"
9
+ "github.com/go-acme/lego/challenge/tlsalpn01"
10
+ "github.com/mholt/certmagic"
11
+)
12
+
13
+// ConnectionPolicies is an ordered group of connection policies;
14
+// the first matching policy will be used to configure TLS
15
+// connections at handshake-time.
16
+type ConnectionPolicies []*ConnectionPolicy
17
+
18
+// TLSConfig converts the group of policies to a standard-lib-compatible
19
+// TLS configuration which selects the first matching policy based on
20
+// the ClientHello.
21
+func (cp ConnectionPolicies) TLSConfig(handle caddy2.Handle) (*tls.Config, error) {
22
+ // connection policy matchers
23
+ for i, pol := range cp {
24
+ for modName, rawMsg := range pol.MatchersRaw {
25
+ val, err := caddy2.LoadModule("tls.handshake_match."+modName, rawMsg)
26
+ if err != nil {
27
+ return nil, fmt.Errorf("loading handshake matcher module '%s': %s", modName, err)
28
+ }
29
+ cp[i].Matchers = append(cp[i].Matchers, val.(ConnectionMatcher))
30
+ }
31
+ cp[i].MatchersRaw = nil // allow GC to deallocate - TODO: Does this help?
32
+ }
33
+
34
+ // pre-build standard TLS configs so we don't have to at handshake-time
35
+ for i := range cp {
36
+ err := cp[i].buildStandardTLSConfig(handle)
37
+ if err != nil {
38
+ return nil, fmt.Errorf("connection policy %d: building standard TLS config: %s", i, err)
39
+ }
40
+ }
41
+
42
+ return &tls.Config{
43
+ GetConfigForClient: func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
44
+ policyLoop:
45
+ for _, pol := range cp {
46
+ for _, matcher := range pol.Matchers {
47
+ if !matcher.Match(hello) {
48
+ continue policyLoop
49
+ }
50
+ }
51
+ return pol.stdTLSConfig, nil
52
+ }
53
+ return nil, fmt.Errorf("no server TLS configuration available for ClientHello: %+v", hello)
54
+ },
55
+ }, nil
56
+}
57
+
58
+// ConnectionPolicy specifies the logic for handling a TLS handshake.
59
+type ConnectionPolicy struct {
60
+ MatchersRaw map[string]json.RawMessage `json:"match,omitempty"`
61
+
62
+ CipherSuites []string `json:"cipher_suites,omitempty"`
63
+ Curves []string `json:"curves,omitempty"`
64
+ ALPN []string `json:"alpn,omitempty"`
65
+ ProtocolMin string `json:"protocol_min,omitempty"`
66
+ ProtocolMax string `json:"protocol_max,omitempty"`
67
+
68
+ // TODO: Client auth
69
+
70
+ // TODO: see if starlark could be useful here - enterprise only
71
+ StarlarkHandshake string `json:"starlark_handshake,omitempty"`
72
+
73
+ Matchers []ConnectionMatcher
74
+ stdTLSConfig *tls.Config
75
+}
76
+
77
+func (cp *ConnectionPolicy) buildStandardTLSConfig(handle caddy2.Handle) error {
78
+ tlsApp := handle.App("tls").(*TLS)
79
+
80
+ cfg := &tls.Config{
81
+ NextProtos: cp.ALPN,
82
+ PreferServerCipherSuites: true,
83
+ GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
84
+ cfgTpl, err := tlsApp.getConfigForName(hello.ServerName)
85
+ if err != nil {
86
+ return nil, fmt.Errorf("getting config for name %s: %v", hello.ServerName, err)
87
+ }
88
+ newCfg := certmagic.New(tlsApp.certCache, cfgTpl)
89
+ return newCfg.GetCertificate(hello)
90
+ },
91
+ MinVersion: tls.VersionTLS12,
92
+ MaxVersion: tls.VersionTLS13,
93
+ // TODO: Session ticket key rotation (use Storage)
94
+ }
95
+
96
+ // add all the cipher suites in order, without duplicates
97
+ cipherSuitesAdded := make(map[uint16]struct{})
98
+ for _, csName := range cp.CipherSuites {
99
+ csID := supportedCipherSuites[csName]
100
+ if _, ok := cipherSuitesAdded[csID]; !ok {
101
+ cipherSuitesAdded[csID] = struct{}{}
102
+ cfg.CipherSuites = append(cfg.CipherSuites, csID)
103
+ }
104
+ }
105
+
106
+ // add all the curve preferences in order, without duplicates
107
+ curvesAdded := make(map[tls.CurveID]struct{})
108
+ for _, curveName := range cp.Curves {
109
+ curveID := supportedCurves[curveName]
110
+ if _, ok := curvesAdded[curveID]; !ok {
111
+ curvesAdded[curveID] = struct{}{}
112
+ cfg.CurvePreferences = append(cfg.CurvePreferences, curveID)
113
+ }
114
+ }
115
+
116
+ // ensure ALPN includes the ACME TLS-ALPN protocol
117
+ var alpnFound bool
118
+ for _, a := range cp.ALPN {
119
+ if a == tlsalpn01.ACMETLS1Protocol {
120
+ alpnFound = true
121
+ break
122
+ }
123
+ }
124
+ if !alpnFound {
125
+ cfg.NextProtos = append(cfg.NextProtos, tlsalpn01.ACMETLS1Protocol)
126
+ }
127
+
128
+ // min and max protocol versions
129
+ if cp.ProtocolMin != "" {
130
+ cfg.MinVersion = supportedProtocols[cp.ProtocolMin]
131
+ }
132
+ if cp.ProtocolMax != "" {
133
+ cfg.MaxVersion = supportedProtocols[cp.ProtocolMax]
134
+ }
135
+ if cp.ProtocolMin > cp.ProtocolMax {
136
+ return fmt.Errorf("protocol min (%x) cannot be greater than protocol max (%x)", cp.ProtocolMin, cp.ProtocolMax)
137
+ }
138
+
139
+ // TODO: client auth, and other fields
140
+
141
+ cp.stdTLSConfig = cfg
142
+
143
+ return nil
144
+}
145
+
146
+// ConnectionMatcher is a type which matches TLS handshakes.
147
+type ConnectionMatcher interface {
148
+ Match(*tls.ClientHelloInfo) bool
149
+}
modules/caddytls/fileloader.go
new
+61
@@ -0,0 +1,61 @@
1
+package caddytls
2
+
3
+import (
4
+ "crypto/tls"
5
+ "fmt"
6
+ "io/ioutil"
7
+
8
+ "bitbucket.org/lightcodelabs/caddy2"
9
+)
10
+
11
+func init() {
12
+ caddy2.RegisterModule(caddy2.Module{
13
+ Name: "tls.certificates.load_files",
14
+ New: func() (interface{}, error) { return fileLoader{}, nil },
15
+ })
16
+}
17
+
18
+// fileLoader loads certificates and their associated keys from disk.
19
+type fileLoader []CertKeyFilePair
20
+
21
+// CertKeyFilePair pairs certificate and key file names along with their
22
+// encoding format so that they can be loaded from disk.
23
+type CertKeyFilePair struct {
24
+ Certificate string `json:"certificate"`
25
+ Key string `json:"key"`
26
+ Format string `json:"format,omitempty"` // "pem" is default
27
+}
28
+
29
+// LoadCertificates returns the certificates to be loaded by fl.
30
+func (fl fileLoader) LoadCertificates() ([]tls.Certificate, error) {
31
+ var certs []tls.Certificate
32
+ for _, pair := range fl {
33
+ certData, err := ioutil.ReadFile(pair.Certificate)
34
+ if err != nil {
35
+ return nil, err
36
+ }
37
+ keyData, err := ioutil.ReadFile(pair.Key)
38
+ if err != nil {
39
+ return nil, err
40
+ }
41
+
42
+ var cert tls.Certificate
43
+ switch pair.Format {
44
+ case "":
45
+ fallthrough
46
+ case "pem":
47
+ cert, err = tls.X509KeyPair(certData, keyData)
48
+ default:
49
+ return nil, fmt.Errorf("unrecognized certificate/key encoding format: %s", pair.Format)
50
+ }
51
+ if err != nil {
52
+ return nil, err
53
+ }
54
+
55
+ certs = append(certs, cert)
56
+ }
57
+ return certs, nil
58
+}
59
+
60
+// Interface guard
61
+var _ CertificateLoader = (fileLoader)(nil)
modules/caddytls/folderloader.go
new
+122
@@ -0,0 +1,122 @@
1
+package caddytls
2
+
3
+import (
4
+ "bytes"
5
+ "crypto/tls"
6
+ "encoding/pem"
7
+ "fmt"
8
+ "io/ioutil"
9
+ "os"
10
+ "path/filepath"
11
+ "strings"
12
+
13
+ "bitbucket.org/lightcodelabs/caddy2"
14
+)
15
+
16
+func init() {
17
+ caddy2.RegisterModule(caddy2.Module{
18
+ Name: "tls.certificates.load_folders",
19
+ New: func() (interface{}, error) { return folderLoader{}, nil },
20
+ })
21
+}
22
+
23
+// folderLoader loads certificates and their associated keys from disk
24
+// by recursively walking the specified directories, looking for PEM
25
+// files which contain both a certificate and a key.
26
+type folderLoader []string
27
+
28
+// LoadCertificates loads all the certificates+keys in the directories
29
+// listed in fl from all files ending with .pem. This method of loading
30
+// certificates expects the certificate and key to be bundled into the
31
+// same file.
32
+func (fl folderLoader) LoadCertificates() ([]tls.Certificate, error) {
33
+ var certs []tls.Certificate
34
+ for _, dir := range fl {
35
+ err := filepath.Walk(dir, func(fpath string, info os.FileInfo, err error) error {
36
+ if err != nil {
37
+ return fmt.Errorf("unable to traverse into path: %s", fpath)
38
+ }
39
+ if info.IsDir() {
40
+ return nil
41
+ }
42
+ if !strings.HasSuffix(strings.ToLower(info.Name()), ".pem") {
43
+ return nil
44
+ }
45
+
46
+ cert, err := x509CertFromCertAndKeyPEMFile(fpath)
47
+ if err != nil {
48
+ return err
49
+ }
50
+
51
+ certs = append(certs, cert)
52
+
53
+ return nil
54
+ })
55
+ if err != nil {
56
+ return nil, err
57
+ }
58
+ }
59
+ return certs, nil
60
+}
61
+
62
+func x509CertFromCertAndKeyPEMFile(fpath string) (tls.Certificate, error) {
63
+ bundle, err := ioutil.ReadFile(fpath)
64
+ if err != nil {
65
+ return tls.Certificate{}, err
66
+ }
67
+
68
+ certBuilder, keyBuilder := new(bytes.Buffer), new(bytes.Buffer)
69
+ var foundKey bool // use only the first key in the file
70
+
71
+ for {
72
+ // Decode next block so we can see what type it is
73
+ var derBlock *pem.Block
74
+ derBlock, bundle = pem.Decode(bundle)
75
+ if derBlock == nil {
76
+ break
77
+ }
78
+
79
+ if derBlock.Type == "CERTIFICATE" {
80
+ // Re-encode certificate as PEM, appending to certificate chain
81
+ pem.Encode(certBuilder, derBlock)
82
+ } else if derBlock.Type == "EC PARAMETERS" {
83
+ // EC keys generated from openssl can be composed of two blocks:
84
+ // parameters and key (parameter block should come first)
85
+ if !foundKey {
86
+ // Encode parameters
87
+ pem.Encode(keyBuilder, derBlock)
88
+
89
+ // Key must immediately follow
90
+ derBlock, bundle = pem.Decode(bundle)
91
+ if derBlock == nil || derBlock.Type != "EC PRIVATE KEY" {
92
+ return tls.Certificate{}, fmt.Errorf("%s: expected elliptic private key to immediately follow EC parameters", fpath)
93
+ }
94
+ pem.Encode(keyBuilder, derBlock)
95
+ foundKey = true
96
+ }
97
+ } else if derBlock.Type == "PRIVATE KEY" || strings.HasSuffix(derBlock.Type, " PRIVATE KEY") {
98
+ // RSA key
99
+ if !foundKey {
100
+ pem.Encode(keyBuilder, derBlock)
101
+ foundKey = true
102
+ }
103
+ } else {
104
+ return tls.Certificate{}, fmt.Errorf("%s: unrecognized PEM block type: %s", fpath, derBlock.Type)
105
+ }
106
+ }
107
+
108
+ certPEMBytes, keyPEMBytes := certBuilder.Bytes(), keyBuilder.Bytes()
109
+ if len(certPEMBytes) == 0 {
110
+ return tls.Certificate{}, fmt.Errorf("%s: failed to parse PEM data", fpath)
111
+ }
112
+ if len(keyPEMBytes) == 0 {
113
+ return tls.Certificate{}, fmt.Errorf("%s: no private key block found", fpath)
114
+ }
115
+
116
+ cert, err := tls.X509KeyPair(certPEMBytes, keyPEMBytes)
117
+ if err != nil {
118
+ return tls.Certificate{}, fmt.Errorf("%s: making X509 key pair: %v", fpath, err)
119
+ }
120
+
121
+ return cert, nil
122
+}
modules/caddytls/matchers.go
new
+79
@@ -0,0 +1,79 @@
1
+package caddytls
2
+
3
+import (
4
+ "crypto/tls"
5
+
6
+ "bitbucket.org/lightcodelabs/caddy2"
7
+)
8
+
9
+type (
10
+ MatchServerName []string
11
+
12
+ // TODO: these others should be enterprise-only, probably
13
+ MatchProtocol []string // TODO: version or protocol?
14
+ MatchClientCert struct{} // TODO: client certificate options
15
+ MatchRemote []string
16
+ MatchStarlark string
17
+)
18
+
19
+func init() {
20
+ caddy2.RegisterModule(caddy2.Module{
21
+ Name: "tls.handshake_match.host",
22
+ New: func() (interface{}, error) { return MatchServerName{}, nil },
23
+ })
24
+ caddy2.RegisterModule(caddy2.Module{
25
+ Name: "tls.handshake_match.protocol",
26
+ New: func() (interface{}, error) { return MatchProtocol{}, nil },
27
+ })
28
+ caddy2.RegisterModule(caddy2.Module{
29
+ Name: "tls.handshake_match.client_cert",
30
+ New: func() (interface{}, error) { return MatchClientCert{}, nil },
31
+ })
32
+ caddy2.RegisterModule(caddy2.Module{
33
+ Name: "tls.handshake_match.remote",
34
+ New: func() (interface{}, error) { return MatchRemote{}, nil },
35
+ })
36
+ caddy2.RegisterModule(caddy2.Module{
37
+ Name: "tls.handshake_match.starlark",
38
+ New: func() (interface{}, error) { return new(MatchStarlark), nil },
39
+ })
40
+}
41
+
42
+func (m MatchServerName) Match(hello *tls.ClientHelloInfo) bool {
43
+ for _, name := range m {
44
+ // TODO: support wildcards (and regex?)
45
+ if hello.ServerName == name {
46
+ return true
47
+ }
48
+ }
49
+ return false
50
+}
51
+
52
+func (m MatchProtocol) Match(hello *tls.ClientHelloInfo) bool {
53
+ // TODO: not implemented
54
+ return false
55
+}
56
+
57
+func (m MatchClientCert) Match(hello *tls.ClientHelloInfo) bool {
58
+ // TODO: not implemented
59
+ return false
60
+}
61
+
62
+func (m MatchRemote) Match(hello *tls.ClientHelloInfo) bool {
63
+ // TODO: not implemented
64
+ return false
65
+}
66
+
67
+func (m MatchStarlark) Match(hello *tls.ClientHelloInfo) bool {
68
+ // TODO: not implemented
69
+ return false
70
+}
71
+
72
+// Interface guards
73
+var (
74
+ _ ConnectionMatcher = MatchServerName{}
75
+ _ ConnectionMatcher = MatchProtocol{}
76
+ _ ConnectionMatcher = MatchClientCert{}
77
+ _ ConnectionMatcher = MatchRemote{}
78
+ _ ConnectionMatcher = new(MatchStarlark)
79
+)
modules/caddytls/tls.go
new
+359
@@ -0,0 +1,359 @@
1
+package caddytls
2
+
3
+import (
4
+ "crypto/tls"
5
+ "encoding/json"
6
+ "fmt"
7
+ "net/http"
8
+ "time"
9
+
10
+ "bitbucket.org/lightcodelabs/caddy2"
11
+ "github.com/go-acme/lego/certcrypto"
12
+ "github.com/go-acme/lego/challenge"
13
+ "github.com/klauspost/cpuid"
14
+ "github.com/mholt/certmagic"
15
+)
16
+
17
+func init() {
18
+ caddy2.RegisterModule(caddy2.Module{
19
+ Name: "tls",
20
+ New: func() (interface{}, error) { return new(TLS), nil },
21
+ })
22
+}
23
+
24
+// TLS represents a process-wide TLS configuration.
25
+type TLS struct {
26
+ Certificates map[string]json.RawMessage `json:"certificates"`
27
+ Automation AutomationConfig `json:"automation"`
28
+
29
+ certificateLoaders []CertificateLoader
30
+ certCache *certmagic.Cache
31
+}
32
+
33
+// TODO: Finish stubbing out this two-phase setup process: prepare, then start...
34
+
35
+func (t *TLS) Provision() error {
36
+ // set up the certificate cache
37
+ // TODO: this makes a new cache every time; better to only make a new
38
+ // cache (or even better, add/remove only what is necessary) if the
39
+ // certificates config has been updated
40
+ t.certCache = certmagic.NewCache(certmagic.CacheOptions{
41
+ GetConfigForCert: func(cert certmagic.Certificate) (certmagic.Config, error) {
42
+ return t.getConfigForName(cert.Names[0])
43
+ },
44
+ })
45
+
46
+ for i, ap := range t.Automation.Policies {
47
+ val, err := caddy2.LoadModuleInline("module", "tls.management", ap.Management)
48
+ if err != nil {
49
+ return fmt.Errorf("loading TLS automation management module: %s", err)
50
+ }
51
+ t.Automation.Policies[i].management = val.(ManagerMaker)
52
+ t.Automation.Policies[i].Management = nil // allow GC to deallocate - TODO: Does this help?
53
+ }
54
+
55
+ // certificate loaders
56
+ for modName, rawMsg := range t.Certificates {
57
+ if modName == automateKey {
58
+ continue // special case; these will be loaded in later
59
+ }
60
+ val, err := caddy2.LoadModule("tls.certificates."+modName, rawMsg)
61
+ if err != nil {
62
+ return fmt.Errorf("loading certificate module '%s': %s", modName, err)
63
+ }
64
+ t.certificateLoaders = append(t.certificateLoaders, val.(CertificateLoader))
65
+ }
66
+
67
+ return nil
68
+}
69
+
70
+// Start activates the TLS module.
71
+func (t *TLS) Start(handle caddy2.Handle) error {
72
+ // load manual/static (unmanaged) certificates
73
+ for _, loader := range t.certificateLoaders {
74
+ certs, err := loader.LoadCertificates()
75
+ if err != nil {
76
+ return fmt.Errorf("loading certificates: %v", err)
77
+ }
78
+ magic := certmagic.New(t.certCache, certmagic.Config{
79
+ Storage: caddy2.GetStorage(),
80
+ })
81
+ for _, cert := range certs {
82
+ err := magic.CacheUnmanagedTLSCertificate(cert)
83
+ if err != nil {
84
+ return fmt.Errorf("caching unmanaged certificate: %v", err)
85
+ }
86
+ }
87
+ }
88
+
89
+ // load automated (managed) certificates
90
+ if automatedRawMsg, ok := t.Certificates[automateKey]; ok {
91
+ var names []string
92
+ err := json.Unmarshal(automatedRawMsg, &names)
93
+ if err != nil {
94
+ return fmt.Errorf("automate: decoding names: %v", err)
95
+ }
96
+ err = t.Manage(names)
97
+ if err != nil {
98
+ return fmt.Errorf("automate: managing %v: %v", names, err)
99
+ }
100
+ // for _, name := range names {
101
+ // t.Manage([]string{name)
102
+ // ap := t.getAutomationPolicyForName(name)
103
+ // magic := certmagic.New(t.certCache, ap.makeCertMagicConfig())
104
+ // err := magic.Manage([]string{name})
105
+ // if err != nil {
106
+ // return fmt.Errorf("automate: manage %s: %v", name, err)
107
+ // }
108
+ // }
109
+ }
110
+ t.Certificates = nil // allow GC to deallocate - TODO: Does this help?
111
+
112
+ return nil
113
+}
114
+
115
+// Stop stops the TLS module and cleans up any allocations.
116
+func (t *TLS) Stop() error {
117
+ if t.certCache != nil {
118
+ // TODO: ensure locks are cleaned up too... maybe in certmagic though
119
+ t.certCache.Stop()
120
+ }
121
+ return nil
122
+}
123
+
124
+// Manage immediately begins managing names according to the
125
+// matching automation policy.
126
+func (t *TLS) Manage(names []string) error {
127
+ for _, name := range names {
128
+ ap := t.getAutomationPolicyForName(name)
129
+ magic := certmagic.New(t.certCache, ap.makeCertMagicConfig())
130
+ err := magic.Manage([]string{name})
131
+ if err != nil {
132
+ return fmt.Errorf("automate: manage %s: %v", name, err)
133
+ }
134
+ }
135
+ return nil
136
+}
137
+
138
+// HandleHTTPChallenge ensures that the HTTP challenge is handled for the
139
+// certificate named by r.Host, if it is an HTTP challenge request.
140
+func (t *TLS) HandleHTTPChallenge(w http.ResponseWriter, r *http.Request) bool {
141
+ if !certmagic.LooksLikeHTTPChallenge(r) {
142
+ return false
143
+ }
144
+ ap := t.getAutomationPolicyForName(r.Host)
145
+ magic := certmagic.New(t.certCache, ap.makeCertMagicConfig())
146
+ return magic.HandleHTTPChallenge(w, r)
147
+}
148
+
149
+func (t *TLS) getConfigForName(name string) (certmagic.Config, error) {
150
+ ap := t.getAutomationPolicyForName(name)
151
+ return ap.makeCertMagicConfig(), nil
152
+}
153
+
154
+func (t *TLS) getAutomationPolicyForName(name string) AutomationPolicy {
155
+ for _, ap := range t.Automation.Policies {
156
+ if len(ap.Hosts) == 0 {
157
+ // no host filter is an automatic match
158
+ return ap
159
+ }
160
+ for _, h := range ap.Hosts {
161
+ if h == name {
162
+ return ap
163
+ }
164
+ }
165
+ }
166
+
167
+ // default automation policy
168
+ mgmt := new(acmeManagerMaker)
169
+ mgmt.setDefaults()
170
+ return AutomationPolicy{management: mgmt}
171
+}
172
+
173
+// CertificateLoader is a type that can load certificates.
174
+type CertificateLoader interface {
175
+ LoadCertificates() ([]tls.Certificate, error)
176
+}
177
+
178
+// AutomationConfig designates configuration for the
179
+// construction and use of ACME clients.
180
+type AutomationConfig struct {
181
+ Policies []AutomationPolicy `json:"policies,omitempty"`
182
+}
183
+
184
+// AutomationPolicy designates the policy for automating the
185
+// management of managed TLS certificates.
186
+type AutomationPolicy struct {
187
+ Hosts []string `json:"hosts,omitempty"`
188
+ Management json.RawMessage `json:"management"`
189
+
190
+ management ManagerMaker
191
+}
192
+
193
+func (ap AutomationPolicy) makeCertMagicConfig() certmagic.Config {
194
+ if acmeMgmt, ok := ap.management.(*acmeManagerMaker); ok {
195
+ // default, which is management via ACME
196
+
197
+ storage := acmeMgmt.storage
198
+ if storage == nil {
199
+ storage = caddy2.GetStorage()
200
+ }
201
+
202
+ var ond *certmagic.OnDemandConfig
203
+ if acmeMgmt.OnDemand != nil {
204
+ ond = &certmagic.OnDemandConfig{
205
+ // TODO: fill this out
206
+ }
207
+ }
208
+
209
+ return certmagic.Config{
210
+ CA: certmagic.LetsEncryptStagingCA, //ap.CA, // TODO: Restore true value
211
+ Email: acmeMgmt.Email,
212
+ Agreed: true,
213
+ DisableHTTPChallenge: acmeMgmt.Challenges.HTTP.Disabled,
214
+ DisableTLSALPNChallenge: acmeMgmt.Challenges.TLSALPN.Disabled,
215
+ RenewDurationBefore: time.Duration(acmeMgmt.RenewAhead),
216
+ AltHTTPPort: acmeMgmt.Challenges.HTTP.AlternatePort,
217
+ AltTLSALPNPort: acmeMgmt.Challenges.TLSALPN.AlternatePort,
218
+ DNSProvider: acmeMgmt.Challenges.dns,
219
+ KeyType: supportedCertKeyTypes[acmeMgmt.KeyType],
220
+ CertObtainTimeout: time.Duration(acmeMgmt.ACMETimeout),
221
+ OnDemand: ond,
222
+ MustStaple: acmeMgmt.MustStaple,
223
+ Storage: storage,
224
+ // TODO: listenHost
225
+ }
226
+ }
227
+
228
+ return certmagic.Config{
229
+ NewManager: ap.management.newManager,
230
+ }
231
+}
232
+
233
+// ChallengesConfig configures the ACME challenges.
234
+type ChallengesConfig struct {
235
+ HTTP HTTPChallengeConfig `json:"http"`
236
+ TLSALPN TLSALPNChallengeConfig `json:"tls-alpn"`
237
+ DNS json.RawMessage `json:"dns,omitempty"`
238
+
239
+ dns challenge.Provider
240
+}
241
+
242
+// HTTPChallengeConfig configures the ACME HTTP challenge.
243
+type HTTPChallengeConfig struct {
244
+ Disabled bool `json:"disabled,omitempty"`
245
+ AlternatePort int `json:"alternate_port,omitempty"`
246
+}
247
+
248
+// TLSALPNChallengeConfig configures the ACME TLS-ALPN challenge.
249
+type TLSALPNChallengeConfig struct {
250
+ Disabled bool `json:"disabled,omitempty"`
251
+ AlternatePort int `json:"alternate_port,omitempty"`
252
+}
253
+
254
+// OnDemandConfig configures on-demand TLS, for obtaining
255
+// needed certificates at handshake-time.
256
+type OnDemandConfig struct {
257
+ // TODO: MaxCertificates state might not endure reloads...
258
+ // MaxCertificates int `json:"max_certificates,omitempty"`
259
+ AskURL string `json:"ask_url,omitempty"`
260
+ AskStarlark string `json:"ask_starlark,omitempty"`
261
+}
262
+
263
+// supportedCertKeyTypes is all the key types that are supported
264
+// for certificates that are obtained through ACME.
265
+var supportedCertKeyTypes = map[string]certcrypto.KeyType{
266
+ "RSA2048": certcrypto.RSA2048,
267
+ "RSA4096": certcrypto.RSA4096,
268
+ "P256": certcrypto.EC256,
269
+ "P384": certcrypto.EC384,
270
+}
271
+
272
+// supportedCipherSuites is the unordered map of cipher suite
273
+// string names to their definition in crypto/tls.
274
+// TODO: might not be needed much longer, see:
275
+// https://github.com/golang/go/issues/30325
276
+var supportedCipherSuites = map[string]uint16{
277
+ "ECDHE_ECDSA_AES256_GCM_SHA384": tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
278
+ "ECDHE_RSA_AES256_GCM_SHA384": tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
279
+ "ECDHE_ECDSA_AES128_GCM_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
280
+ "ECDHE_RSA_AES128_GCM_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
281
+ "ECDHE_ECDSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
282
+ "ECDHE_RSA_WITH_CHACHA20_POLY1305": tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
283
+ "ECDHE_RSA_AES256_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
284
+ "ECDHE_RSA_AES128_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
285
+ "ECDHE_ECDSA_AES256_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
286
+ "ECDHE_ECDSA_AES128_CBC_SHA": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
287
+ "RSA_AES256_CBC_SHA": tls.TLS_RSA_WITH_AES_256_CBC_SHA,
288
+ "RSA_AES128_CBC_SHA": tls.TLS_RSA_WITH_AES_128_CBC_SHA,
289
+ "ECDHE_RSA_3DES_EDE_CBC_SHA": tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
290
+ "RSA_3DES_EDE_CBC_SHA": tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
291
+}
292
+
293
+// defaultCipherSuites is the ordered list of all the cipher
294
+// suites we want to support by default, assuming AES-NI
295
+// (hardware acceleration for AES).
296
+var defaultCipherSuitesWithAESNI = []uint16{
297
+ tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
298
+ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
299
+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
300
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
301
+ tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
302
+ tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
303
+}
304
+
305
+// defaultCipherSuites is the ordered list of all the cipher
306
+// suites we want to support by default, assuming lack of
307
+// AES-NI (NO hardware acceleration for AES).
308
+var defaultCipherSuitesWithoutAESNI = []uint16{
309
+ tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
310
+ tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
311
+ tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
312
+ tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
313
+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
314
+ tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
315
+}
316
+
317
+// getOptimalDefaultCipherSuites returns an appropriate cipher
318
+// suite to use depending on the hardware support for AES.
319
+//
320
+// See https://github.com/mholt/caddy/issues/1674
321
+func getOptimalDefaultCipherSuites() []uint16 {
322
+ if cpuid.CPU.AesNi() {
323
+ return defaultCipherSuitesWithAESNI
324
+ }
325
+ return defaultCipherSuitesWithoutAESNI
326
+}
327
+
328
+// supportedCurves is the unordered map of supported curves.
329
+// https://golang.org/pkg/crypto/tls/#CurveID
330
+var supportedCurves = map[string]tls.CurveID{
331
+ "X25519": tls.X25519,
332
+ "P256": tls.CurveP256,
333
+ "P384": tls.CurveP384,
334
+ "P521": tls.CurveP521,
335
+}
336
+
337
+// defaultCurves is the list of only the curves we want to use
338
+// by default, in descending order of preference.
339
+//
340
+// This list should only include curves which are fast by design
341
+// (e.g. X25519) and those for which an optimized assembly
342
+// implementation exists (e.g. P256). The latter ones can be
343
+// found here:
344
+// https://github.com/golang/go/tree/master/src/crypto/elliptic
345
+var defaultCurves = []tls.CurveID{
346
+ tls.X25519,
347
+ tls.CurveP256,
348
+}
349
+
350
+// supportedProtocols is a map of supported protocols.
351
+// HTTP/2 only supports TLS 1.2 and higher.
352
+var supportedProtocols = map[string]uint16{
353
+ "tls1.0": tls.VersionTLS10,
354
+ "tls1.1": tls.VersionTLS11,
355
+ "tls1.2": tls.VersionTLS12,
356
+ "tls1.3": tls.VersionTLS13,
357
+}
358
+
359
+const automateKey = "automate"
storage.go
new
+74
@@ -0,0 +1,74 @@
1
+package caddy2
2
+
3
+import (
4
+ "os"
5
+ "path/filepath"
6
+ "runtime"
7
+
8
+ "github.com/mholt/certmagic"
9
+)
10
+
11
+func init() {
12
+ RegisterModule(Module{
13
+ Name: "caddy.storage.file_system",
14
+ New: func() (interface{}, error) { return new(fileStorage), nil },
15
+ })
16
+}
17
+
18
+// StorageConverter is a type that can convert itself
19
+// to a valid, usable certmagic.Storage value. The
20
+// value might be short-lived.
21
+type StorageConverter interface {
22
+ CertMagicStorage() (certmagic.Storage, error)
23
+}
24
+
25
+// TODO: Wrappers other than file_system should be enterprise-only.
26
+
27
+// It may seem trivial to wrap these, but the benefits are:
28
+// 1. We don't need to change the actual CertMagic storage implementions
29
+// to a structure that is operable with Caddy's config (including JSON
30
+// tags), and
31
+// 2. We don't need to rely on rely on maintainers of third-party
32
+// certmagic.Storage implementations. We can make any certmagic.Storage
33
+// work with Caddy this way.
34
+
35
+// fileStorage is a certmagic.Storage wrapper for certmagic.FileStorage.
36
+type fileStorage struct {
37
+ Root string `json:"root"`
38
+}
39
+
40
+func (s fileStorage) CertMagicStorage() (certmagic.Storage, error) {
41
+ return &certmagic.FileStorage{Path: s.Root}, nil
42
+}
43
+
44
+// homeDir returns the best guess of the current user's home
45
+// directory from environment variables. If unknown, "." (the
46
+// current directory) is returned instead.
47
+func homeDir() string {
48
+ home := os.Getenv("HOME")
49
+ if home == "" && runtime.GOOS == "windows" {
50
+ drive := os.Getenv("HOMEDRIVE")
51
+ path := os.Getenv("HOMEPATH")
52
+ home = drive + path
53
+ if drive == "" || path == "" {
54
+ home = os.Getenv("USERPROFILE")
55
+ }
56
+ }
57
+ if home == "" {
58
+ home = "."
59
+ }
60
+ return home
61
+}
62
+
63
+// dataDir returns a directory path that is suitable for storage.
64
+// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables
65
+func dataDir() string {
66
+ baseDir := filepath.Join(homeDir(), ".local", "share")
67
+ if xdgData := os.Getenv("XDG_DATA_HOME"); xdgData != "" {
68
+ baseDir = xdgData
69
+ }
70
+ return filepath.Join(baseDir, "caddy")
71
+}
72
+
73
+// Interface guard
74
+var _ StorageConverter = fileStorage{}