diff options
Diffstat (limited to 'core/config.go')
-rw-r--r-- | core/config.go | 346 |
1 files changed, 346 insertions, 0 deletions
diff --git a/core/config.go b/core/config.go new file mode 100644 index 000000000..8c376d878 --- /dev/null +++ b/core/config.go @@ -0,0 +1,346 @@ +package core + +import ( + "bytes" + "fmt" + "io" + "log" + "net" + "sync" + + "github.com/miekg/coredns/core/https" + "github.com/miekg/coredns/core/parse" + "github.com/miekg/coredns/core/setup" + "github.com/miekg/coredns/server" +) + +const ( + // DefaultConfigFile is the name of the configuration file that is loaded + // by default if no other file is specified. + DefaultConfigFile = "Corefile" +) + +func loadConfigsUpToIncludingTLS(filename string, input io.Reader) ([]server.Config, []parse.ServerBlock, int, error) { + var configs []server.Config + + // Each server block represents similar hosts/addresses, since they + // were grouped together in the Caddyfile. + serverBlocks, err := parse.ServerBlocks(filename, input, true) + if err != nil { + return nil, nil, 0, err + } + if len(serverBlocks) == 0 { + newInput := DefaultInput() + serverBlocks, err = parse.ServerBlocks(newInput.Path(), bytes.NewReader(newInput.Body()), true) + if err != nil { + return nil, nil, 0, err + } + } + + 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, + // 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 { + onces := makeOnces() + storages := makeStorages() + + for j, addr := range sb.Addresses { + config := server.Config{ + Host: addr.Host, + Port: addr.Port, + Root: Root, + ConfigFile: filename, + AppName: AppName, + AppVersion: AppVersion, + } + + // It is crucial that directives are executed in the proper order. + for k, dir := range directiveOrder { + // Execute directive if it is in the server block + if tokens, ok := sb.Tokens[dir.name]; ok { + // Each setup function gets a controller, from which setup functions + // get access to the config, tokens, and other state information useful + // to set up its own host only. + controller := &setup.Controller{ + Config: &config, + 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], + } + // execute setup function and append middleware handler, if any + midware, err := dir.setup(controller) + if err != nil { + return nil, nil, lastDirectiveIndex, err + } + if midware != nil { + config.Middleware = append(config.Middleware, midware) + } + 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) + } + } + return configs, serverBlocks, lastDirectiveIndex, nil +} + +// loadConfigs reads input (named filename) and parses it, returning the +// server configurations in the order they appeared in the input. As part +// of this, it activates Let's Encrypt for the configs that are produced. +// Thus, the returned configs are already optimally configured for HTTPS. +func loadConfigs(filename string, input io.Reader) ([]server.Config, error) { + configs, serverBlocks, lastDirectiveIndex, err := loadConfigsUpToIncludingTLS(filename, input) + if err != nil { + return nil, err + } + + // 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 only appends, so our iterations + // over server blocks below shouldn't be affected) + if !IsRestart() && !Quiet { + fmt.Println("Activating privacy features...") + } + /* TODO(miek): stopped for now + configs, err = https.Activate(configs) + if err != nil { + return nil, err + } else if !IsRestart() && !Quiet { + fmt.Println(" done.") + } + */ + + // 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 { + configs[configIndex].Middleware = append(configs[configIndex].Middleware, midware) + } + storages[dir.name] = controller.ServerBlockStorage // persist for this server block + } + } + } + } + + return configs, nil +} + +// makeOnces makes a map of directive name to sync.Once +// instance. This is intended to be called once per server +// block when setting up configs so that Setup functions +// for each directive can perform a task just once per +// server block, even if there are multiple hosts on the block. +// +// We need one Once per directive, otherwise the first +// directive to use it would exclude other directives from +// using it at all, which would be a bug. +func makeOnces() map[string]*sync.Once { + onces := make(map[string]*sync.Once) + for _, dir := range directiveOrder { + onces[dir.name] = new(sync.Once) + } + return onces +} + +// makeStorages makes a map of directive name to interface{} +// so that directives' setup functions can persist state +// between different hosts on the same server block during the +// setup phase. +func makeStorages() map[string]interface{} { + storages := make(map[string]interface{}) + for _, dir := range directiveOrder { + storages[dir.name] = nil + } + return storages +} + +// arrangeBindings groups configurations by their bind address. For example, +// a server that should listen on localhost and another on 127.0.0.1 will +// be grouped into the same address: 127.0.0.1. It will return an error +// if an address is malformed or a TLS listener is configured on the +// same address as a plaintext HTTP listener. The return value is a map of +// bind address to list of configs that would become VirtualHosts on that +// server. Use the keys of the returned map to create listeners, and use +// the associated values to set up the virtualhosts. +func arrangeBindings(allConfigs []server.Config) (bindingGroup, error) { + var groupings bindingGroup + + // Group configs by bind address + for _, conf := range allConfigs { + // use default port if none is specified + if conf.Port == "" { + conf.Port = Port + } + + bindAddr, warnErr, fatalErr := resolveAddr(conf) + if fatalErr != nil { + return groupings, fatalErr + } + if warnErr != nil { + log.Printf("[WARNING] Resolving bind address for %s: %v", conf.Address(), warnErr) + } + + // Make sure to compare the string representation of the address, + // not the pointer, since a new *TCPAddr is created each time. + var existing bool + for i := 0; i < len(groupings); i++ { + if groupings[i].BindAddr.String() == bindAddr.String() { + groupings[i].Configs = append(groupings[i].Configs, conf) + existing = true + break + } + } + if !existing { + groupings = append(groupings, bindingMapping{ + BindAddr: bindAddr, + Configs: []server.Config{conf}, + }) + } + } + + // Don't allow HTTP and HTTPS to be served on the same address + for _, group := range groupings { + isTLS := group.Configs[0].TLS.Enabled + for _, config := range group.Configs { + if config.TLS.Enabled != isTLS { + thisConfigProto, otherConfigProto := "HTTP", "HTTP" + if config.TLS.Enabled { + thisConfigProto = "HTTPS" + } + if group.Configs[0].TLS.Enabled { + otherConfigProto = "HTTPS" + } + return groupings, fmt.Errorf("configuration error: Cannot multiplex %s (%s) and %s (%s) on same address", + group.Configs[0].Address(), otherConfigProto, config.Address(), thisConfigProto) + } + } + } + + return groupings, nil +} + +// resolveAddr determines the address (host and port) that a config will +// bind to. The returned address, resolvAddr, should be used to bind the +// listener or group the config with other configs using the same address. +// The first error, if not nil, is just a warning and should be reported +// but execution may continue. The second error, if not nil, is a real +// problem and the server should not be started. +// +// This function does not handle edge cases like port "http" or "https" if +// they are not known to the system. It does, however, serve on the wildcard +// host if resolving the address of the specific hostname fails. +func resolveAddr(conf server.Config) (resolvAddr *net.TCPAddr, warnErr, fatalErr error) { + resolvAddr, warnErr = net.ResolveTCPAddr("tcp", net.JoinHostPort(conf.BindHost, conf.Port)) + if warnErr != nil { + // the hostname probably couldn't be resolved, just bind to wildcard then + resolvAddr, fatalErr = net.ResolveTCPAddr("tcp", net.JoinHostPort("", conf.Port)) + if fatalErr != nil { + return + } + } + + return +} + +// validDirective returns true if d is a valid +// directive; false otherwise. +func validDirective(d string) bool { + for _, dir := range directiveOrder { + if dir.name == d { + return true + } + } + return false +} + +// DefaultInput returns the default Caddyfile input +// to use when it is otherwise empty or missing. +// It uses the default host and port (depends on +// host, e.g. localhost is 2015, otherwise 443) and +// root. +func DefaultInput() CaddyfileInput { + port := Port + if https.HostQualifies(Host) && port == DefaultPort { + port = "443" + } + return CaddyfileInput{ + Contents: []byte(fmt.Sprintf("%s:%s\nroot %s", Host, port, Root)), + } +} + +// These defaults are configurable through the command line +var ( + // Root is the site root + Root = DefaultRoot + + // Host is the site host + Host = DefaultHost + + // Port is the site port + Port = DefaultPort +) + +// bindingMapping maps a network address to configurations +// that will bind to it. The order of the configs is important. +type bindingMapping struct { + BindAddr *net.TCPAddr + Configs []server.Config +} + +// bindingGroup maps network addresses to their configurations. +// Preserving the order of the groupings is important +// (related to graceful shutdown and restart) +// so this is a slice, not a literal map. +type bindingGroup []bindingMapping |