Commit 5e1573dd authored by Matthew Holt's avatar Matthew Holt

Better error handling at startup and fixed some bugs

Fixed bug where manually specifying port 443 disabled TLS (whoops); otherHostHasScheme was the culprit, since it would return true even if it was the same config that had that scheme.

Also, an error at startup (if not a restart) is now fatal, rather than keeping a half-alive zombie server.
parent e8006acf
......@@ -127,7 +127,7 @@ func Start(cdyfile Input) error {
}
// Close remaining file descriptors we may have inherited that we don't need
if isRestart() {
if IsRestart() {
for _, fdIndex := range loadedGob.ListenerFds {
file := os.NewFile(fdIndex, "")
fln, err := net.FileListener(file)
......@@ -138,7 +138,7 @@ func Start(cdyfile Input) error {
}
// Show initialization output
if !Quiet && !isRestart() {
if !Quiet && !IsRestart() {
var checkedFdLimit bool
for _, group := range groupings {
for _, conf := range group.Configs {
......@@ -159,7 +159,7 @@ func Start(cdyfile Input) error {
}
// Tell parent process that we got this
if isRestart() {
if IsRestart() {
ppipe := os.NewFile(3, "") // parent is listening on pipe at index 3
ppipe.Write([]byte("success"))
ppipe.Close()
......@@ -174,6 +174,7 @@ func Start(cdyfile Input) error {
// until the servers are listening.
func startServers(groupings Group) error {
var startupWg sync.WaitGroup
errChan := make(chan error)
for _, group := range groupings {
s, err := server.New(group.BindAddr.String(), group.Configs)
......@@ -183,7 +184,7 @@ func startServers(groupings Group) error {
s.HTTP2 = HTTP2 // TODO: This setting is temporary
var ln server.ListenerFile
if isRestart() {
if IsRestart() {
// Look up this server's listener in the map of inherited file descriptors;
// if we don't have one, we must make a new one.
if fdIndex, ok := loadedGob.ListenerFds[s.Addr]; ok {
......@@ -215,11 +216,7 @@ func startServers(groupings Group) error {
// "use of closed network connection" is normal if doing graceful shutdown...
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
if isRestart() {
log.Fatal(err)
} else {
log.Println(err)
}
errChan <- err
}
}(s, ln)
......@@ -234,8 +231,16 @@ func startServers(groupings Group) error {
serversMu.Unlock()
}
// Wait for all servers to finish starting
startupWg.Wait()
// Return the first error, if any
select {
case err := <-errChan:
return err
default:
}
return nil
}
......@@ -269,7 +274,7 @@ func Wait() {
func LoadCaddyfile(loader func() (Input, error)) (cdyfile Input, err error) {
// If we are a fork, finishing the restart is highest priority;
// piped input is required in this case.
if isRestart() {
if IsRestart() {
err := gob.NewDecoder(os.Stdin).Decode(&loadedGob)
if err != nil {
return nil, err
......
......@@ -46,9 +46,9 @@ type caddyfileGob struct {
Caddyfile Input
}
// isRestart returns whether this process is, according
// IsRestart returns whether this process is, according
// to env variables, a fork as part of a graceful restart.
func isRestart() bool {
func IsRestart() bool {
return os.Getenv("CADDY_RESTART") == "true"
}
......
......@@ -57,8 +57,8 @@ func Activate(configs []server.Config) ([]server.Config, error) {
// we already have certs and keys in storage from last time.
configLen := len(configs) // avoid infinite loop since this loop appends plaintext to the slice
for i := 0; i < configLen; i++ {
if existingCertAndKey(configs[i].Host) && configQualifies(configs[i], configs) {
configs = autoConfigure(&configs[i], configs)
if existingCertAndKey(configs[i].Host) && configQualifies(configs, i) {
configs = autoConfigure(configs, i)
}
}
......@@ -123,7 +123,7 @@ func Activate(configs []server.Config) ([]server.Config, error) {
// it all comes down to this: turning on TLS with all the new certs
for i := 0; i < len(serverConfigs); i++ {
configs = autoConfigure(serverConfigs[i], configs)
configs = autoConfigure(configs, i)
}
}
......@@ -151,10 +151,11 @@ func Deactivate() (err error) {
return
}
// configQualifies returns true if cfg qualifes for automatic LE activation,
// but it does require the list of all configs to be passed in as well.
// It does NOT check to see if a cert and key already exist for cfg.
func configQualifies(cfg server.Config, allConfigs []server.Config) bool {
// configQualifies returns true if the config at cfgIndex (within allConfigs)
// qualifes for automatic LE activation. It does NOT check to see if a cert
// and key already exist for the config.
func configQualifies(allConfigs []server.Config, cfgIndex int) bool {
cfg := allConfigs[cfgIndex]
return cfg.TLS.Certificate == "" && // user could provide their own cert and key
cfg.TLS.Key == "" &&
......@@ -167,11 +168,11 @@ func configQualifies(cfg server.Config, allConfigs []server.Config) bool {
cfg.Host != "" &&
cfg.Host != "0.0.0.0" &&
cfg.Host != "::1" &&
!strings.HasPrefix(cfg.Host, "127.") && // to use a boulder on your own machine, add fake domain to hosts file
!strings.HasPrefix(cfg.Host, "127.") && // to use boulder on your own machine, add fake domain to hosts file
// not excluding 10.* and 192.168.* hosts for possibility of running internal Boulder instance
// make sure an HTTPS version of this config doesn't exist in the list already
!hostHasOtherScheme(cfg.Host, "https", allConfigs)
// make sure another HTTPS version of this config doesn't exist in the list already
!otherHostHasScheme(allConfigs, cfgIndex, "https")
}
// groupConfigsByEmail groups configs by user email address. The returned map is
......@@ -186,7 +187,7 @@ func groupConfigsByEmail(configs []server.Config) (map[string][]*server.Config,
// that we won't be obtaining certs for - this way we won't
// bother the user for an email address unnecessarily and
// we don't obtain new certs for a host we already have certs for.
if existingCertAndKey(configs[i].Host) || !configQualifies(configs[i], configs) {
if existingCertAndKey(configs[i].Host) || !configQualifies(configs, i) {
continue
}
leEmail := getEmail(configs[i])
......@@ -311,12 +312,14 @@ func saveCertsAndKeys(certificates []acme.CertificateResource) error {
return nil
}
// autoConfigure enables TLS on cfg and appends, if necessary, a new config
// to allConfigs that redirects plaintext HTTP to its new HTTPS counterpart.
// It expects the certificate and key to already be in storage. It returns
// the new list of allConfigs, since it may append a new config. This function
// assumes that cfg was already set up for HTTPS.
func autoConfigure(cfg *server.Config, allConfigs []server.Config) []server.Config {
// autoConfigure enables TLS on allConfigs[cfgIndex] and appends, if necessary,
// a new config to allConfigs that redirects plaintext HTTP to its new HTTPS
// counterpart. It expects the certificate and key to already be in storage. It
// returns the new list of allConfigs, since it may append a new config. This
// function assumes that allConfigs[cfgIndex] is already set up for HTTPS.
func autoConfigure(allConfigs []server.Config, cfgIndex int) []server.Config {
cfg := &allConfigs[cfgIndex]
bundleBytes, err := ioutil.ReadFile(storage.SiteCertFile(cfg.Host))
// TODO: Handle these errors better
if err == nil {
......@@ -344,28 +347,32 @@ func autoConfigure(cfg *server.Config, allConfigs []server.Config) []server.Conf
acmeHandlers[cfg.Host] = handler
}
// Set up http->https redirect as long as there isn't already
// a http counterpart in the configs
if !hostHasOtherScheme(cfg.Host, "http", allConfigs) {
// Set up http->https redirect as long as there isn't already a http counterpart
// in the configs and this isn't, for some reason, already on port 80
if !otherHostHasScheme(allConfigs, cfgIndex, "http") &&
cfg.Port != "80" && cfg.Port != "http" { // (would not be http port with current program flow, but just in case)
allConfigs = append(allConfigs, redirPlaintextHost(*cfg))
}
return allConfigs
}
// hostHasOtherScheme tells you whether there is another config in the list
// for the same host but with the port equal to scheme. For example, to see
// if example.com has a https variant already, pass in example.com and
// "https" along with the list of configs. This function considers "443"
// and "https" to be the same scheme, as well as "http" and "80".
func hostHasOtherScheme(host, scheme string, allConfigs []server.Config) bool {
// otherHostHasScheme tells you whether there is ANOTHER config in allConfigs
// for the same host but with the port equal to scheme as allConfigs[cfgIndex].
// This function considers "443" and "https" to be the same scheme, as well as
// "http" and "80". It does not tell you whether there is ANY config with scheme,
// only if there's a different one with it.
func otherHostHasScheme(allConfigs []server.Config, cfgIndex int, scheme string) bool {
if scheme == "80" {
scheme = "http"
} else if scheme == "443" {
scheme = "https"
}
for _, otherCfg := range allConfigs {
if otherCfg.Host == host {
for i, otherCfg := range allConfigs {
if i == cfgIndex {
continue // has to be a config OTHER than the one we're comparing against
}
if otherCfg.Host == allConfigs[cfgIndex].Host {
if (otherCfg.Port == scheme) ||
(scheme == "https" && otherCfg.Port == "443") ||
(scheme == "http" && otherCfg.Port == "80") {
......
......@@ -79,7 +79,11 @@ func main() {
// Start your engines
err = caddy.Start(caddyfile)
if err != nil {
log.Fatal(err)
if caddy.IsRestart() {
log.Println("error starting servers:", err)
} else {
log.Fatal(err)
}
}
// Twiddle your thumbs
......
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