@marcel91200 / caddy Mirror · read-only / commits / 2d056fbe
Fork 0

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{}