Commit a729be29 authored by Matthew Holt's avatar Matthew Holt

letsencrypt: Activate during config load just after tls directive

Before, we were activating Let's Encrypt after all the directives were executed. This means their setup functions had access to potentially erroneous information about the server's TLS setup, since the letsencrypt package makes changes to the port, etc. Now, we execute all directives up to and including tls, then activate letsencrypt, then finish with the rest of the directives. It's a bit ugly, but I do think it is more correct. It also fixes some bugs, for example: a host that only has a catch-all redirect.
parent 4d71620c
...@@ -113,14 +113,8 @@ func Start(cdyfile Input) error { ...@@ -113,14 +113,8 @@ func Start(cdyfile Input) error {
caddyfile = cdyfile caddyfile = cdyfile
caddyfileMu.Unlock() caddyfileMu.Unlock()
// load the server configs // load the server configs (activates Let's Encrypt)
configs, err := load(path.Base(cdyfile.Path()), bytes.NewReader(cdyfile.Body())) configs, err := loadConfigs(path.Base(cdyfile.Path()), bytes.NewReader(cdyfile.Body()))
if err != nil {
return err
}
// secure all the things
configs, err = letsencrypt.Activate(configs)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -7,6 +7,7 @@ import ( ...@@ -7,6 +7,7 @@ import (
"net" "net"
"sync" "sync"
"github.com/mholt/caddy/caddy/letsencrypt"
"github.com/mholt/caddy/caddy/parse" "github.com/mholt/caddy/caddy/parse"
"github.com/mholt/caddy/caddy/setup" "github.com/mholt/caddy/caddy/setup"
"github.com/mholt/caddy/middleware" "github.com/mholt/caddy/middleware"
...@@ -19,15 +20,20 @@ const ( ...@@ -19,15 +20,20 @@ const (
DefaultConfigFile = "Caddyfile" DefaultConfigFile = "Caddyfile"
) )
// load reads input (named filename) and parses it, returning the // loadConfigs reads input (named filename) and parses it, returning the
// server configurations in the order they appeared in the input. // server configurations in the order they appeared in the input. As part
func load(filename string, input io.Reader) ([]server.Config, error) { // of this, it activates Let's Encrypt for the configs that are produced.
// Thus, the returned configs are already optimally configured optimally
// for HTTPS.
func loadConfigs(filename string, input io.Reader) ([]server.Config, error) {
var configs []server.Config var configs []server.Config
// turn off timestamp for parsing // turn off timestamp for parsing
flags := log.Flags() flags := log.Flags()
log.SetFlags(0) log.SetFlags(0)
// Each server block represents similar hosts/addresses, since they
// were grouped together in the Caddyfile.
serverBlocks, err := parse.ServerBlocks(filename, input, true) serverBlocks, err := parse.ServerBlocks(filename, input, true)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -36,9 +42,11 @@ func load(filename string, input io.Reader) ([]server.Config, error) { ...@@ -36,9 +42,11 @@ func load(filename string, input io.Reader) ([]server.Config, error) {
return []server.Config{NewDefault()}, nil return []server.Config{NewDefault()}, nil
} }
// Each server block represents similar hosts/addresses. var lastDirectiveIndex int // we set up directives in two parts; this stores where we left off
// Iterate each server block and make a config for each one, // Iterate each server block and make a config for each one,
// executing the directives that were parsed. // executing the directives that were parsed in order up to the tls
// directive; this is because we must activate Let's Encrypt.
for i, sb := range serverBlocks { for i, sb := range serverBlocks {
onces := makeOnces() onces := makeOnces()
storages := makeStorages() storages := makeStorages()
...@@ -55,12 +63,12 @@ func load(filename string, input io.Reader) ([]server.Config, error) { ...@@ -55,12 +63,12 @@ func load(filename string, input io.Reader) ([]server.Config, error) {
} }
// It is crucial that directives are executed in the proper order. // It is crucial that directives are executed in the proper order.
for _, dir := range directiveOrder { for k, dir := range directiveOrder {
// Execute directive if it is in the server block // Execute directive if it is in the server block
if tokens, ok := sb.Tokens[dir.name]; ok { if tokens, ok := sb.Tokens[dir.name]; ok {
// Each setup function gets a controller, which is the // Each setup function gets a controller, from which setup functions
// server config and the dispenser containing only // get access to the config, tokens, and other state information useful
// this directive's tokens. // to set up its own host only.
controller := &setup.Controller{ controller := &setup.Controller{
Config: &config, Config: &config,
Dispenser: parse.NewDispenserTokens(filename, tokens), Dispenser: parse.NewDispenserTokens(filename, tokens),
...@@ -76,7 +84,7 @@ func load(filename string, input io.Reader) ([]server.Config, error) { ...@@ -76,7 +84,7 @@ func load(filename string, input io.Reader) ([]server.Config, error) {
ServerBlockHosts: sb.HostList(), ServerBlockHosts: sb.HostList(),
ServerBlockStorage: storages[dir.name], ServerBlockStorage: storages[dir.name],
} }
// execute setup function and append middleware handler, if any
midware, err := dir.setup(controller) midware, err := dir.setup(controller)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -87,12 +95,74 @@ func load(filename string, input io.Reader) ([]server.Config, error) { ...@@ -87,12 +95,74 @@ func load(filename string, input io.Reader) ([]server.Config, error) {
} }
storages[dir.name] = controller.ServerBlockStorage // persist for this server block storages[dir.name] = controller.ServerBlockStorage // persist for this server block
} }
// Stop after TLS setup, since we need to activate Let's Encrypt before continuing;
// it makes some changes to the configs that middlewares might want to know about.
if dir.name == "tls" {
lastDirectiveIndex = k
break
}
} }
configs = append(configs, config) configs = append(configs, config)
} }
} }
// Now we have all the configs, but they have only been set up to the
// point of tls. We need to activate Let's Encrypt before setting up
// the rest of the middlewares so they have correct information regarding
// TLS configuration, if necessary. (this call is append-only, so our
// iterations below shouldn't be affected)
configs, err = letsencrypt.Activate(configs)
if err != nil {
return nil, err
}
// Finish setting up the rest of the directives, now that TLS is
// optimally configured. These loops are similar to above except
// we don't iterate all the directives from the beginning and we
// don't create new configs.
configIndex := -1
for i, sb := range serverBlocks {
onces := makeOnces()
storages := makeStorages()
for j := range sb.Addresses {
configIndex++
for k := lastDirectiveIndex + 1; k < len(directiveOrder); k++ {
dir := directiveOrder[k]
if tokens, ok := sb.Tokens[dir.name]; ok {
controller := &setup.Controller{
Config: &configs[configIndex],
Dispenser: parse.NewDispenserTokens(filename, tokens),
OncePerServerBlock: func(f func() error) error {
var err error
onces[dir.name].Do(func() {
err = f()
})
return err
},
ServerBlockIndex: i,
ServerBlockHostIndex: j,
ServerBlockHosts: sb.HostList(),
ServerBlockStorage: storages[dir.name],
}
midware, err := dir.setup(controller)
if err != nil {
return nil, err
}
if midware != nil {
// TODO: For now, we only support the default path scope /
configs[configIndex].Middleware["/"] = append(configs[configIndex].Middleware["/"], midware)
}
storages[dir.name] = controller.ServerBlockStorage // persist for this server block
}
}
}
}
// restore logging settings // restore logging settings
log.SetFlags(flags) log.SetFlags(flags)
......
...@@ -42,7 +42,7 @@ func init() { ...@@ -42,7 +42,7 @@ func init() {
var directiveOrder = []directive{ var directiveOrder = []directive{
// Essential directives that initialize vital configuration settings // Essential directives that initialize vital configuration settings
{"root", setup.Root}, {"root", setup.Root},
{"tls", setup.TLS}, {"tls", setup.TLS}, // letsencrypt is set up just after tls
{"bind", setup.BindHost}, {"bind", setup.BindHost},
// Other directives that don't create HTTP handlers // Other directives that don't create HTTP handlers
......
...@@ -40,10 +40,16 @@ import ( ...@@ -40,10 +40,16 @@ import (
// Activate returns the updated list of configs, since // Activate returns the updated list of configs, since
// some may have been appended, for example, to redirect // some may have been appended, for example, to redirect
// plaintext HTTP requests to their HTTPS counterpart. // plaintext HTTP requests to their HTTPS counterpart.
// This function only appends; it does not prepend or splice.
func Activate(configs []server.Config) ([]server.Config, error) { func Activate(configs []server.Config) ([]server.Config, error) {
// just in case previous caller forgot... // just in case previous caller forgot...
Deactivate() Deactivate()
// TODO: All the output the end user should see when running caddy is something
// simple like "Setting up HTTPS..." (and maybe 'done' at the end of the line when finished).
// In other words, hide all the other logging except for on errors. Or maybe
// have a place to put those logs.
// reset cached ocsp statuses from any previous activations // reset cached ocsp statuses from any previous activations
ocspStatus = make(map[*[]byte]int) ocspStatus = make(map[*[]byte]int)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment