Commit e3f2d96a authored by Matthew Holt's avatar Matthew Holt

httpserver: Flags to customize HTTP and HTTPS ports (incl. for ACME)

This commit removes _almost_ all instances of hard-coded ports 80 and
443 strings, and now allows the user to define what the HTTP and HTTPS
ports are by the -http-port and -https-ports flags.

(One instance of "80" is still hard-coded in tls.go because it cannot
import httpserver to get access to the HTTP port variable. I don't
suspect this will be a problem in practice, but one workaround would be
to define an exported variable in the caddytls package and let the
httpserver package set it as well as its own HTTPPort variable.)

The port numbers required by the ACME challenges HTTP-01 and TLS-SNI-01
are hard-coded into the spec as ports 80 and 443 for good reasons,
but the big question is whether they necessarily need to be the HTTP
and HTTPS ports. Although the answer is probably no, they chose those
ports for convenience and widest compatibility/deployability. So this
commit also assumes that the "HTTP port" is necessarily the same port
on which to serve the HTTP-01 challenge, and the "HTTPS port" is
necessarily the same one on which to serve the TLS-SNI-01 challenge. In
other words, changing the HTTP and HTTPS ports also changes the ports
the challenges will be served on.

If you change the HTTP and HTTPS ports, you are responsible for
configuring your system to forward ports 80 and 443 properly.

Closes #918 and closes #1293. Also related: #468.
parent 0a0d2cc1
...@@ -111,7 +111,7 @@ func (c Context) Port() (string, error) { ...@@ -111,7 +111,7 @@ func (c Context) Port() (string, error) {
if err != nil { if err != nil {
if !strings.Contains(c.Req.Host, ":") { if !strings.Contains(c.Req.Host, ":") {
// common with sites served on the default port 80 // common with sites served on the default port 80
return "80", nil return HTTPPort, nil
} }
return "", err return "", err
} }
......
...@@ -93,7 +93,7 @@ func enableAutoHTTPS(configs []*SiteConfig, loadCertificates bool) error { ...@@ -93,7 +93,7 @@ func enableAutoHTTPS(configs []*SiteConfig, loadCertificates bool) error {
cfg.TLS.Enabled && cfg.TLS.Enabled &&
(!cfg.TLS.Manual || cfg.TLS.OnDemand) && (!cfg.TLS.Manual || cfg.TLS.OnDemand) &&
cfg.Addr.Host != "localhost" { cfg.Addr.Host != "localhost" {
cfg.Addr.Port = "443" cfg.Addr.Port = HTTPSPort
} }
} }
return nil return nil
...@@ -108,8 +108,8 @@ func enableAutoHTTPS(configs []*SiteConfig, loadCertificates bool) error { ...@@ -108,8 +108,8 @@ func enableAutoHTTPS(configs []*SiteConfig, loadCertificates bool) error {
func makePlaintextRedirects(allConfigs []*SiteConfig) []*SiteConfig { func makePlaintextRedirects(allConfigs []*SiteConfig) []*SiteConfig {
for i, cfg := range allConfigs { for i, cfg := range allConfigs {
if cfg.TLS.Managed && if cfg.TLS.Managed &&
!hostHasOtherPort(allConfigs, i, "80") && !hostHasOtherPort(allConfigs, i, HTTPPort) &&
(cfg.Addr.Port == "443" || !hostHasOtherPort(allConfigs, i, "443")) { (cfg.Addr.Port == HTTPSPort || !hostHasOtherPort(allConfigs, i, HTTPSPort)) {
allConfigs = append(allConfigs, redirPlaintextHost(cfg)) allConfigs = append(allConfigs, redirPlaintextHost(cfg))
} }
} }
...@@ -135,18 +135,19 @@ func hostHasOtherPort(allConfigs []*SiteConfig, thisConfigIdx int, otherPort str ...@@ -135,18 +135,19 @@ func hostHasOtherPort(allConfigs []*SiteConfig, thisConfigIdx int, otherPort str
// redirPlaintextHost returns a new plaintext HTTP configuration for // redirPlaintextHost returns a new plaintext HTTP configuration for
// a virtualHost that simply redirects to cfg, which is assumed to // a virtualHost that simply redirects to cfg, which is assumed to
// be the HTTPS configuration. The returned configuration is set // be the HTTPS configuration. The returned configuration is set
// to listen on port 80. The TLS field of cfg must not be nil. // to listen on HTTPPort. The TLS field of cfg must not be nil.
func redirPlaintextHost(cfg *SiteConfig) *SiteConfig { func redirPlaintextHost(cfg *SiteConfig) *SiteConfig {
redirPort := cfg.Addr.Port redirPort := cfg.Addr.Port
if redirPort == "443" { if redirPort == DefaultHTTPSPort {
// default port is redundant redirPort = "" // default port is redundant
redirPort = ""
} }
redirMiddleware := func(next Handler) Handler { redirMiddleware := func(next Handler) Handler {
return HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) { return HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
toURL := "https://" + r.Host toURL := "https://"
if redirPort != "" { if redirPort == "" {
toURL += ":" + redirPort toURL += cfg.Addr.Host // don't use r.Host as it may have a port included
} else {
toURL += net.JoinHostPort(cfg.Addr.Host, redirPort)
} }
toURL += r.URL.RequestURI() toURL += r.URL.RequestURI()
w.Header().Set("Connection", "close") w.Header().Set("Connection", "close")
...@@ -155,12 +156,13 @@ func redirPlaintextHost(cfg *SiteConfig) *SiteConfig { ...@@ -155,12 +156,13 @@ func redirPlaintextHost(cfg *SiteConfig) *SiteConfig {
}) })
} }
host := cfg.Addr.Host host := cfg.Addr.Host
port := "80" port := HTTPPort
addr := net.JoinHostPort(host, port) addr := net.JoinHostPort(host, port)
return &SiteConfig{ return &SiteConfig{
Addr: Address{Original: addr, Host: host, Port: port}, Addr: Address{Original: addr, Host: host, Port: port},
ListenHost: cfg.ListenHost, ListenHost: cfg.ListenHost,
middleware: []Middleware{redirMiddleware}, middleware: []Middleware{redirMiddleware},
TLS: &caddytls.Config{AltHTTPPort: cfg.TLS.AltHTTPPort}, TLS: &caddytls.Config{AltHTTPPort: cfg.TLS.AltHTTPPort, AltTLSSNIPort: cfg.TLS.AltTLSSNIPort},
Timeouts: cfg.Timeouts,
} }
} }
...@@ -11,7 +11,7 @@ import ( ...@@ -11,7 +11,7 @@ import (
func TestRedirPlaintextHost(t *testing.T) { func TestRedirPlaintextHost(t *testing.T) {
cfg := redirPlaintextHost(&SiteConfig{ cfg := redirPlaintextHost(&SiteConfig{
Addr: Address{ Addr: Address{
Host: "example.com", Host: "foohost",
Port: "1234", Port: "1234",
}, },
ListenHost: "93.184.216.34", ListenHost: "93.184.216.34",
...@@ -19,7 +19,7 @@ func TestRedirPlaintextHost(t *testing.T) { ...@@ -19,7 +19,7 @@ func TestRedirPlaintextHost(t *testing.T) {
}) })
// Check host and port // Check host and port
if actual, expected := cfg.Addr.Host, "example.com"; actual != expected { if actual, expected := cfg.Addr.Host, "foohost"; actual != expected {
t.Errorf("Expected redir config to have host %s but got %s", expected, actual) t.Errorf("Expected redir config to have host %s but got %s", expected, actual)
} }
if actual, expected := cfg.ListenHost, "93.184.216.34"; actual != expected { if actual, expected := cfg.ListenHost, "93.184.216.34"; actual != expected {
...@@ -38,7 +38,7 @@ func TestRedirPlaintextHost(t *testing.T) { ...@@ -38,7 +38,7 @@ func TestRedirPlaintextHost(t *testing.T) {
// Check redirect for correctness // Check redirect for correctness
rec := httptest.NewRecorder() rec := httptest.NewRecorder()
req, err := http.NewRequest("GET", "http://foo/bar?q=1", nil) req, err := http.NewRequest("GET", "http://foohost/bar?q=1", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -52,17 +52,17 @@ func TestRedirPlaintextHost(t *testing.T) { ...@@ -52,17 +52,17 @@ func TestRedirPlaintextHost(t *testing.T) {
if rec.Code != http.StatusMovedPermanently { if rec.Code != http.StatusMovedPermanently {
t.Errorf("Expected status %d but got %d", http.StatusMovedPermanently, rec.Code) t.Errorf("Expected status %d but got %d", http.StatusMovedPermanently, rec.Code)
} }
if got, want := rec.Header().Get("Location"), "https://foo:1234/bar?q=1"; got != want { if got, want := rec.Header().Get("Location"), "https://foohost:1234/bar?q=1"; got != want {
t.Errorf("Expected Location: '%s' but got '%s'", want, got) t.Errorf("Expected Location: '%s' but got '%s'", want, got)
} }
// browsers can infer a default port from scheme, so make sure the port // browsers can infer a default port from scheme, so make sure the port
// doesn't get added in explicitly for default ports like 443 for https. // doesn't get added in explicitly for default ports like 443 for https.
cfg = redirPlaintextHost(&SiteConfig{Addr: Address{Host: "example.com", Port: "443"}, TLS: new(caddytls.Config)}) cfg = redirPlaintextHost(&SiteConfig{Addr: Address{Host: "foohost", Port: "443"}, TLS: new(caddytls.Config)})
handler = cfg.middleware[0](nil) handler = cfg.middleware[0](nil)
rec = httptest.NewRecorder() rec = httptest.NewRecorder()
req, err = http.NewRequest("GET", "http://foo/bar?q=1", nil) req, err = http.NewRequest("GET", "http://foohost/bar?q=1", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -76,7 +76,7 @@ func TestRedirPlaintextHost(t *testing.T) { ...@@ -76,7 +76,7 @@ func TestRedirPlaintextHost(t *testing.T) {
if rec.Code != http.StatusMovedPermanently { if rec.Code != http.StatusMovedPermanently {
t.Errorf("Expected status %d but got %d", http.StatusMovedPermanently, rec.Code) t.Errorf("Expected status %d but got %d", http.StatusMovedPermanently, rec.Code)
} }
if got, want := rec.Header().Get("Location"), "https://foo/bar?q=1"; got != want { if got, want := rec.Header().Get("Location"), "https://foohost/bar?q=1"; got != want {
t.Errorf("Expected Location: '%s' but got '%s'", want, got) t.Errorf("Expected Location: '%s' but got '%s'", want, got)
} }
} }
......
...@@ -19,6 +19,8 @@ import ( ...@@ -19,6 +19,8 @@ import (
const serverType = "http" const serverType = "http"
func init() { func init() {
flag.StringVar(&HTTPPort, "http-port", HTTPPort, "Default port to use for HTTP")
flag.StringVar(&HTTPSPort, "https-port", HTTPSPort, "Default port to use for HTTPS")
flag.StringVar(&Host, "host", DefaultHost, "Default host") flag.StringVar(&Host, "host", DefaultHost, "Default host")
flag.StringVar(&Port, "port", DefaultPort, "Default port") flag.StringVar(&Port, "port", DefaultPort, "Default port")
flag.StringVar(&Root, "root", DefaultRoot, "Root path of default site") flag.StringVar(&Root, "root", DefaultRoot, "Root path of default site")
...@@ -119,11 +121,25 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd ...@@ -119,11 +121,25 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd
addr.Port = Port addr.Port = Port
} }
// If default HTTP or HTTPS ports have been customized,
// make sure the ACME challenge ports match
var altHTTPPort, altTLSSNIPort string
if HTTPPort != DefaultHTTPPort {
altHTTPPort = HTTPPort
}
if HTTPSPort != DefaultHTTPSPort {
altTLSSNIPort = HTTPSPort
}
// Save the config to our master list, and key it for lookups // Save the config to our master list, and key it for lookups
cfg := &SiteConfig{ cfg := &SiteConfig{
Addr: addr, Addr: addr,
Root: Root, Root: Root,
TLS: &caddytls.Config{Hostname: addr.Host}, TLS: &caddytls.Config{
Hostname: addr.Host,
AltHTTPPort: altHTTPPort,
AltTLSSNIPort: altTLSSNIPort,
},
originCaddyfile: sourceFile, originCaddyfile: sourceFile,
} }
h.saveConfig(key, cfg) h.saveConfig(key, cfg)
...@@ -154,7 +170,7 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) { ...@@ -154,7 +170,7 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) {
if !cfg.TLS.Enabled { if !cfg.TLS.Enabled {
continue continue
} }
if cfg.Addr.Port == "80" || cfg.Addr.Scheme == "http" { if cfg.Addr.Port == HTTPPort || cfg.Addr.Scheme == "http" {
cfg.TLS.Enabled = false cfg.TLS.Enabled = false
log.Printf("[WARNING] TLS disabled for %s", cfg.Addr) log.Printf("[WARNING] TLS disabled for %s", cfg.Addr)
} else if cfg.Addr.Scheme == "" { } else if cfg.Addr.Scheme == "" {
...@@ -169,7 +185,7 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) { ...@@ -169,7 +185,7 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) {
// this is vital, otherwise the function call below that // this is vital, otherwise the function call below that
// sets the listener address will use the default port // sets the listener address will use the default port
// instead of 443 because it doesn't know about TLS. // instead of 443 because it doesn't know about TLS.
cfg.Addr.Port = "443" cfg.Addr.Port = HTTPSPort
} }
} }
...@@ -270,7 +286,7 @@ func (a Address) String() string { ...@@ -270,7 +286,7 @@ func (a Address) String() string {
} }
scheme := a.Scheme scheme := a.Scheme
if scheme == "" { if scheme == "" {
if a.Port == "443" { if a.Port == HTTPSPort {
scheme = "https" scheme = "https"
} else { } else {
scheme = "http" scheme = "http"
...@@ -282,8 +298,8 @@ func (a Address) String() string { ...@@ -282,8 +298,8 @@ func (a Address) String() string {
} }
s += a.Host s += a.Host
if a.Port != "" && if a.Port != "" &&
((scheme == "https" && a.Port != "443") || ((scheme == "https" && a.Port != DefaultHTTPSPort) ||
(scheme == "http" && a.Port != "80")) { (scheme == "http" && a.Port != DefaultHTTPPort)) {
s += ":" + a.Port s += ":" + a.Port
} }
if a.Path != "" { if a.Path != "" {
...@@ -327,9 +343,9 @@ func standardizeAddress(str string) (Address, error) { ...@@ -327,9 +343,9 @@ func standardizeAddress(str string) (Address, error) {
// see if we can set port based off scheme // see if we can set port based off scheme
if port == "" { if port == "" {
if u.Scheme == "http" { if u.Scheme == "http" {
port = "80" port = HTTPPort
} else if u.Scheme == "https" { } else if u.Scheme == "https" {
port = "443" port = HTTPSPort
} }
} }
...@@ -339,17 +355,17 @@ func standardizeAddress(str string) (Address, error) { ...@@ -339,17 +355,17 @@ func standardizeAddress(str string) (Address, error) {
} }
// error if scheme and port combination violate convention // error if scheme and port combination violate convention
if (u.Scheme == "http" && port == "443") || (u.Scheme == "https" && port == "80") { if (u.Scheme == "http" && port == HTTPSPort) || (u.Scheme == "https" && port == HTTPPort) {
return Address{}, fmt.Errorf("[%s] scheme and port violate convention", input) return Address{}, fmt.Errorf("[%s] scheme and port violate convention", input)
} }
// standardize http and https ports to their respective port numbers // standardize http and https ports to their respective port numbers
if port == "http" { if port == "http" {
u.Scheme = "http" u.Scheme = "http"
port = "80" port = HTTPPort
} else if port == "https" { } else if port == "https" {
u.Scheme = "https" u.Scheme = "https"
port = "443" port = HTTPSPort
} }
return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err
...@@ -477,6 +493,10 @@ const ( ...@@ -477,6 +493,10 @@ const (
DefaultPort = "2015" DefaultPort = "2015"
// DefaultRoot is the default root folder. // DefaultRoot is the default root folder.
DefaultRoot = "." DefaultRoot = "."
// DefaultHTTPPort is the default port for HTTP.
DefaultHTTPPort = "80"
// DefaultHTTPSPort is the default port for HTTPS.
DefaultHTTPSPort = "443"
) )
// These "soft defaults" are configurable by // These "soft defaults" are configurable by
...@@ -499,4 +519,10 @@ var ( ...@@ -499,4 +519,10 @@ var (
// QUIC indicates whether QUIC is enabled or not. // QUIC indicates whether QUIC is enabled or not.
QUIC bool QUIC bool
// HTTPPort is the port to use for HTTP.
HTTPPort = DefaultHTTPPort
// HTTPSPort is the port to use for HTTPS.
HTTPSPort = DefaultHTTPSPort
) )
...@@ -112,33 +112,39 @@ var newACMEClient = func(config *Config, allowPrompts bool) (*ACMEClient, error) ...@@ -112,33 +112,39 @@ var newACMEClient = func(config *Config, allowPrompts bool) (*ACMEClient, error)
// Use HTTP and TLS-SNI challenges by default // Use HTTP and TLS-SNI challenges by default
// See if HTTP challenge needs to be proxied // See if HTTP challenge needs to be proxied
useHTTPPort := "" // empty port value will use challenge default useHTTPPort := HTTPChallengePort
if caddy.HasListenerWithAddress(net.JoinHostPort(config.ListenHost, HTTPChallengePort)) { if config.AltHTTPPort != "" {
useHTTPPort = config.AltHTTPPort useHTTPPort = config.AltHTTPPort
if useHTTPPort == "" { }
useHTTPPort = DefaultHTTPAlternatePort if caddy.HasListenerWithAddress(net.JoinHostPort(config.ListenHost, useHTTPPort)) {
} useHTTPPort = DefaultHTTPAlternatePort
}
// See which port TLS-SNI challenges will be accomplished on
useTLSSNIPort := TLSSNIChallengePort
if config.AltTLSSNIPort != "" {
useTLSSNIPort = config.AltTLSSNIPort
} }
// Always respect user's bind preferences by using config.ListenHost. // Always respect user's bind preferences by using config.ListenHost.
// NOTE(Sep'16): At time of writing, SetHTTPAddress() and SetTLSaddress() // NOTE(Sep'16): At time of writing, SetHTTPAddress() and SetTLSAddress()
// must be called before SetChallengeProvider(), since they reset the // must be called before SetChallengeProvider(), since they reset the
// challenge provider back to the default one! // challenge provider back to the default one!
err := c.acmeClient.SetHTTPAddress(net.JoinHostPort(config.ListenHost, useHTTPPort)) err := c.acmeClient.SetHTTPAddress(net.JoinHostPort(config.ListenHost, useHTTPPort))
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = c.acmeClient.SetTLSAddress(net.JoinHostPort(config.ListenHost, "")) err = c.acmeClient.SetTLSAddress(net.JoinHostPort(config.ListenHost, useTLSSNIPort))
if err != nil { if err != nil {
return nil, err return nil, err
} }
// See if TLS challenge needs to be handled by our own facilities // See if TLS challenge needs to be handled by our own facilities
if caddy.HasListenerWithAddress(net.JoinHostPort(config.ListenHost, TLSSNIChallengePort)) { if caddy.HasListenerWithAddress(net.JoinHostPort(config.ListenHost, useTLSSNIPort)) {
c.acmeClient.SetChallengeProvider(acme.TLSSNI01, tlsSniSolver{}) c.acmeClient.SetChallengeProvider(acme.TLSSNI01, tlsSniSolver{})
} }
} else { } else {
// Otherwise, DNS challenge it is // Otherwise, use DNS challenge exclusively
// Load provider constructor function // Load provider constructor function
provFn, ok := dnsProviders[config.DNSProvider] provFn, ok := dnsProviders[config.DNSProvider]
......
...@@ -84,6 +84,12 @@ type Config struct { ...@@ -84,6 +84,12 @@ type Config struct {
// coming in on port 80 to this alternate port // coming in on port 80 to this alternate port
AltHTTPPort string AltHTTPPort string
// The alternate port (ONLY port, not host)
// to use for the ACME TLS-SNI challenge.
// The system must forward the standard port
// for the TLS-SNI challenge to this port.
AltTLSSNIPort string
// The string identifier of the DNS provider // The string identifier of the DNS provider
// to use when solving the ACME DNS challenge // to use when solving the ACME DNS challenge
DNSProvider string DNSProvider string
...@@ -479,11 +485,11 @@ var defaultCurves = []tls.CurveID{ ...@@ -479,11 +485,11 @@ var defaultCurves = []tls.CurveID{
const ( const (
// HTTPChallengePort is the officially designated port for // HTTPChallengePort is the officially designated port for
// the HTTP challenge. // the HTTP challenge according to the ACME spec.
HTTPChallengePort = "80" HTTPChallengePort = "80"
// TLSSNIChallengePort is the officially designated port for // TLSSNIChallengePort is the officially designated port for
// the TLS-SNI challenge. // the TLS-SNI challenge according to the ACME spec.
TLSSNIChallengePort = "443" TLSSNIChallengePort = "443"
// DefaultHTTPAlternatePort is the port on which the ACME // DefaultHTTPAlternatePort is the port on which the ACME
......
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