Merge pull request #1500 from mholt/customports

httpserver: Flags to customize HTTP and HTTPS ports (including for ACME challenges)
This commit is contained in:
Matt Holt 2017-03-07 11:31:47 -07:00 committed by GitHub
commit df9d062a8f
6 changed files with 84 additions and 44 deletions

View File

@ -134,7 +134,7 @@ func (c Context) Port() (string, error) {
if err != nil {
if !strings.Contains(c.Req.Host, ":") {
// common with sites served on the default port 80
return "80", nil
return HTTPPort, nil
}
return "", err
}

View File

@ -93,7 +93,7 @@ func enableAutoHTTPS(configs []*SiteConfig, loadCertificates bool) error {
cfg.TLS.Enabled &&
(!cfg.TLS.Manual || cfg.TLS.OnDemand) &&
cfg.Addr.Host != "localhost" {
cfg.Addr.Port = "443"
cfg.Addr.Port = HTTPSPort
}
}
return nil
@ -108,8 +108,8 @@ func enableAutoHTTPS(configs []*SiteConfig, loadCertificates bool) error {
func makePlaintextRedirects(allConfigs []*SiteConfig) []*SiteConfig {
for i, cfg := range allConfigs {
if cfg.TLS.Managed &&
!hostHasOtherPort(allConfigs, i, "80") &&
(cfg.Addr.Port == "443" || !hostHasOtherPort(allConfigs, i, "443")) {
!hostHasOtherPort(allConfigs, i, HTTPPort) &&
(cfg.Addr.Port == HTTPSPort || !hostHasOtherPort(allConfigs, i, HTTPSPort)) {
allConfigs = append(allConfigs, redirPlaintextHost(cfg))
}
}
@ -135,18 +135,19 @@ func hostHasOtherPort(allConfigs []*SiteConfig, thisConfigIdx int, otherPort str
// redirPlaintextHost returns a new plaintext HTTP configuration for
// a virtualHost that simply redirects to cfg, which is assumed to
// 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 {
redirPort := cfg.Addr.Port
if redirPort == "443" {
// default port is redundant
redirPort = ""
if redirPort == DefaultHTTPSPort {
redirPort = "" // default port is redundant
}
redirMiddleware := func(next Handler) Handler {
return HandlerFunc(func(w http.ResponseWriter, r *http.Request) (int, error) {
toURL := "https://" + r.Host
if redirPort != "" {
toURL += ":" + redirPort
toURL := "https://"
if 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()
w.Header().Set("Connection", "close")
@ -155,12 +156,13 @@ func redirPlaintextHost(cfg *SiteConfig) *SiteConfig {
})
}
host := cfg.Addr.Host
port := "80"
port := HTTPPort
addr := net.JoinHostPort(host, port)
return &SiteConfig{
Addr: Address{Original: addr, Host: host, Port: port},
ListenHost: cfg.ListenHost,
middleware: []Middleware{redirMiddleware},
TLS: &caddytls.Config{AltHTTPPort: cfg.TLS.AltHTTPPort},
TLS: &caddytls.Config{AltHTTPPort: cfg.TLS.AltHTTPPort, AltTLSSNIPort: cfg.TLS.AltTLSSNIPort},
Timeouts: cfg.Timeouts,
}
}

View File

@ -11,7 +11,7 @@ import (
func TestRedirPlaintextHost(t *testing.T) {
cfg := redirPlaintextHost(&SiteConfig{
Addr: Address{
Host: "example.com",
Host: "foohost",
Port: "1234",
},
ListenHost: "93.184.216.34",
@ -19,7 +19,7 @@ func TestRedirPlaintextHost(t *testing.T) {
})
// 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)
}
if actual, expected := cfg.ListenHost, "93.184.216.34"; actual != expected {
@ -38,7 +38,7 @@ func TestRedirPlaintextHost(t *testing.T) {
// Check redirect for correctness
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 {
t.Fatal(err)
}
@ -52,17 +52,17 @@ func TestRedirPlaintextHost(t *testing.T) {
if rec.Code != http.StatusMovedPermanently {
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)
}
// 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.
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)
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 {
t.Fatal(err)
}
@ -76,7 +76,7 @@ func TestRedirPlaintextHost(t *testing.T) {
if rec.Code != http.StatusMovedPermanently {
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)
}
}

View File

@ -19,6 +19,8 @@ import (
const serverType = "http"
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(&Port, "port", DefaultPort, "Default port")
flag.StringVar(&Root, "root", DefaultRoot, "Root path of default site")
@ -119,11 +121,25 @@ func (h *httpContext) InspectServerBlocks(sourceFile string, serverBlocks []cadd
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
cfg := &SiteConfig{
Addr: addr,
Root: Root,
TLS: &caddytls.Config{Hostname: addr.Host},
Addr: addr,
Root: Root,
TLS: &caddytls.Config{
Hostname: addr.Host,
AltHTTPPort: altHTTPPort,
AltTLSSNIPort: altTLSSNIPort,
},
originCaddyfile: sourceFile,
}
h.saveConfig(key, cfg)
@ -154,7 +170,7 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) {
if !cfg.TLS.Enabled {
continue
}
if cfg.Addr.Port == "80" || cfg.Addr.Scheme == "http" {
if cfg.Addr.Port == HTTPPort || cfg.Addr.Scheme == "http" {
cfg.TLS.Enabled = false
log.Printf("[WARNING] TLS disabled for %s", cfg.Addr)
} else if cfg.Addr.Scheme == "" {
@ -169,7 +185,7 @@ func (h *httpContext) MakeServers() ([]caddy.Server, error) {
// this is vital, otherwise the function call below that
// sets the listener address will use the default port
// 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 {
}
scheme := a.Scheme
if scheme == "" {
if a.Port == "443" {
if a.Port == HTTPSPort {
scheme = "https"
} else {
scheme = "http"
@ -282,8 +298,8 @@ func (a Address) String() string {
}
s += a.Host
if a.Port != "" &&
((scheme == "https" && a.Port != "443") ||
(scheme == "http" && a.Port != "80")) {
((scheme == "https" && a.Port != DefaultHTTPSPort) ||
(scheme == "http" && a.Port != DefaultHTTPPort)) {
s += ":" + a.Port
}
if a.Path != "" {
@ -327,9 +343,9 @@ func standardizeAddress(str string) (Address, error) {
// see if we can set port based off scheme
if port == "" {
if u.Scheme == "http" {
port = "80"
port = HTTPPort
} else if u.Scheme == "https" {
port = "443"
port = HTTPSPort
}
}
@ -339,17 +355,17 @@ func standardizeAddress(str string) (Address, error) {
}
// 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)
}
// standardize http and https ports to their respective port numbers
if port == "http" {
u.Scheme = "http"
port = "80"
port = HTTPPort
} else if port == "https" {
u.Scheme = "https"
port = "443"
port = HTTPSPort
}
return Address{Original: input, Scheme: u.Scheme, Host: host, Port: port, Path: u.Path}, err
@ -477,6 +493,10 @@ const (
DefaultPort = "2015"
// DefaultRoot is the default root folder.
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
@ -499,4 +519,10 @@ var (
// QUIC indicates whether QUIC is enabled or not.
QUIC bool
// HTTPPort is the port to use for HTTP.
HTTPPort = DefaultHTTPPort
// HTTPSPort is the port to use for HTTPS.
HTTPSPort = DefaultHTTPSPort
)

View File

@ -112,33 +112,39 @@ var newACMEClient = func(config *Config, allowPrompts bool) (*ACMEClient, error)
// Use HTTP and TLS-SNI challenges by default
// See if HTTP challenge needs to be proxied
useHTTPPort := "" // empty port value will use challenge default
if caddy.HasListenerWithAddress(net.JoinHostPort(config.ListenHost, HTTPChallengePort)) {
useHTTPPort := HTTPChallengePort
if 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.
// 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
// challenge provider back to the default one!
err := c.acmeClient.SetHTTPAddress(net.JoinHostPort(config.ListenHost, useHTTPPort))
if err != nil {
return nil, err
}
err = c.acmeClient.SetTLSAddress(net.JoinHostPort(config.ListenHost, ""))
err = c.acmeClient.SetTLSAddress(net.JoinHostPort(config.ListenHost, useTLSSNIPort))
if err != nil {
return nil, err
}
// 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{})
}
} else {
// Otherwise, DNS challenge it is
// Otherwise, use DNS challenge exclusively
// Load provider constructor function
provFn, ok := dnsProviders[config.DNSProvider]

View File

@ -84,6 +84,12 @@ type Config struct {
// coming in on port 80 to this alternate port
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
// to use when solving the ACME DNS challenge
DNSProvider string
@ -479,11 +485,11 @@ var defaultCurves = []tls.CurveID{
const (
// HTTPChallengePort is the officially designated port for
// the HTTP challenge.
// the HTTP challenge according to the ACME spec.
HTTPChallengePort = "80"
// TLSSNIChallengePort is the officially designated port for
// the TLS-SNI challenge.
// the TLS-SNI challenge according to the ACME spec.
TLSSNIChallengePort = "443"
// DefaultHTTPAlternatePort is the port on which the ACME