Commit a7aeb979 authored by Matthew Holt's avatar Matthew Holt

caddytls: Use IP address to find config; vendor: update certmagic

Closes #2356
parent 771dcf3d
...@@ -405,6 +405,8 @@ func groupSiteConfigsByListenAddr(configs []*SiteConfig) (map[string][]*SiteConf ...@@ -405,6 +405,8 @@ func groupSiteConfigsByListenAddr(configs []*SiteConfig) (map[string][]*SiteConf
// parts of an address. The component parts may be // parts of an address. The component parts may be
// updated to the correct values as setup proceeds, // updated to the correct values as setup proceeds,
// but the original value should never be changed. // but the original value should never be changed.
//
// The Host field must be in a normalized form.
type Address struct { type Address struct {
Original, Scheme, Host, Port, Path string Original, Scheme, Host, Port, Path string
} }
...@@ -453,10 +455,17 @@ func (a Address) Normalize() Address { ...@@ -453,10 +455,17 @@ func (a Address) Normalize() Address {
if !CaseSensitivePath { if !CaseSensitivePath {
path = strings.ToLower(path) path = strings.ToLower(path)
} }
// ensure host is normalized if it's an IP address
host := a.Host
if ip := net.ParseIP(host); ip != nil {
host = ip.String()
}
return Address{ return Address{
Original: a.Original, Original: a.Original,
Scheme: strings.ToLower(a.Scheme), Scheme: strings.ToLower(a.Scheme),
Host: strings.ToLower(a.Host), Host: strings.ToLower(host),
Port: a.Port, Port: a.Port,
Path: path, Path: path,
} }
......
...@@ -33,7 +33,9 @@ type Config struct { ...@@ -33,7 +33,9 @@ type Config struct {
// The hostname or class of hostnames this config is // The hostname or class of hostnames this config is
// designated for; can contain wildcard characters // designated for; can contain wildcard characters
// according to RFC 6125 §6.4.3 - this field MUST // according to RFC 6125 §6.4.3 - this field MUST
// be set in order for things to work as expected // be set in order for things to work as expected,
// must be normalized, and if an IP address, must
// be normalized
Hostname string Hostname string
// Whether TLS is enabled // Whether TLS is enabled
...@@ -272,7 +274,7 @@ func MakeTLSConfig(configs []*Config) (*tls.Config, error) { ...@@ -272,7 +274,7 @@ func MakeTLSConfig(configs []*Config) (*tls.Config, error) {
// A tls.Config must have Certificates or GetCertificate // A tls.Config must have Certificates or GetCertificate
// set, in order to be accepted by tls.Listen and quic.Listen. // set, in order to be accepted by tls.Listen and quic.Listen.
// TODO: remove this once the standard library allows a tls.Config with // TODO: remove this once the standard library allows a tls.Config with
// only GetConfigForClient set. // only GetConfigForClient set. https://github.com/mholt/caddy/pull/2404
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) { GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
return nil, fmt.Errorf("all certificates configured via GetConfigForClient") return nil, fmt.Errorf("all certificates configured via GetConfigForClient")
}, },
......
...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
"strings" "strings"
"github.com/mholt/caddy/telemetry" "github.com/mholt/caddy/telemetry"
"github.com/mholt/certmagic"
) )
// configGroup is a type that keys configs by their hostname // configGroup is a type that keys configs by their hostname
...@@ -27,7 +28,7 @@ import ( ...@@ -27,7 +28,7 @@ import (
// method to get a config by matching its hostname). // method to get a config by matching its hostname).
type configGroup map[string]*Config type configGroup map[string]*Config
// getConfig gets the config by the first key match for name. // getConfig gets the config by the first key match for hello.
// In other words, "sub.foo.bar" will get the config for "*.foo.bar" // In other words, "sub.foo.bar" will get the config for "*.foo.bar"
// if that is the closest match. If no match is found, the first // if that is the closest match. If no match is found, the first
// (random) config will be loaded, which will defer any TLS alerts // (random) config will be loaded, which will defer any TLS alerts
...@@ -36,8 +37,8 @@ type configGroup map[string]*Config ...@@ -36,8 +37,8 @@ type configGroup map[string]*Config
// //
// This function follows nearly the same logic to lookup // This function follows nearly the same logic to lookup
// a hostname as the getCertificate function uses. // a hostname as the getCertificate function uses.
func (cg configGroup) getConfig(name string) *Config { func (cg configGroup) getConfig(hello *tls.ClientHelloInfo) *Config {
name = strings.ToLower(name) name := certmagic.CertNameFromClientHello(hello)
// exact match? great, let's use it // exact match? great, let's use it
if config, ok := cg[name]; ok { if config, ok := cg[name]; ok {
...@@ -72,7 +73,7 @@ func (cg configGroup) getConfig(name string) *Config { ...@@ -72,7 +73,7 @@ func (cg configGroup) getConfig(name string) *Config {
// //
// This method is safe for use as a tls.Config.GetConfigForClient callback. // This method is safe for use as a tls.Config.GetConfigForClient callback.
func (cg configGroup) GetConfigForClient(clientHello *tls.ClientHelloInfo) (*tls.Config, error) { func (cg configGroup) GetConfigForClient(clientHello *tls.ClientHelloInfo) (*tls.Config, error) {
config := cg.getConfig(clientHello.ServerName) config := cg.getConfig(clientHello)
if config != nil { if config != nil {
return config.tlsConfig, nil return config.tlsConfig, nil
} }
......
...@@ -369,13 +369,10 @@ func (cfg *Config) preObtainOrRenewChecks(name string, allowPrompts bool) (bool, ...@@ -369,13 +369,10 @@ func (cfg *Config) preObtainOrRenewChecks(name string, allowPrompts bool) (bool,
return true, nil return true, nil
} }
if cfg.Email == "" { err := cfg.getEmail(allowPrompts)
var err error
cfg.Email, err = cfg.getEmail(allowPrompts)
if err != nil { if err != nil {
return false, err return false, err
} }
}
return false, nil return false, nil
} }
......
...@@ -23,12 +23,13 @@ import ( ...@@ -23,12 +23,13 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"net/http"
"os" "os"
"path" "path"
"sort" "sort"
"strings" "strings"
"github.com/xenolf/lego/lego" "github.com/xenolf/lego/acme"
"github.com/xenolf/lego/registration" "github.com/xenolf/lego/registration"
) )
...@@ -71,81 +72,74 @@ func (cfg *Config) newUser(email string) (user, error) { ...@@ -71,81 +72,74 @@ func (cfg *Config) newUser(email string) (user, error) {
// getEmail does everything it can to obtain an email address // getEmail does everything it can to obtain an email address
// from the user within the scope of memory and storage to use // from the user within the scope of memory and storage to use
// for ACME TLS. If it cannot get an email address, it returns // for ACME TLS. If it cannot get an email address, it does nothing
// empty string. (If user is present, it will warn the user of // (If user is prompted, it will warn the user of
// the consequences of an empty email.) This function MAY prompt // the consequences of an empty email.) This function MAY prompt
// the user for input. If userPresent is false, the operator // the user for input. If allowPrompts is false, the user
// will NOT be prompted and an empty email may be returned. // will NOT be prompted and an empty email may be returned.
// If the user is prompted, a new User will be created and func (cfg *Config) getEmail(allowPrompts bool) error {
// stored in storage according to the email address they
// provided (which might be blank).
func (cfg *Config) getEmail(userPresent bool) (string, error) {
// First try memory
leEmail := cfg.Email leEmail := cfg.Email
// First try package default email
if leEmail == "" { if leEmail == "" {
leEmail = Email leEmail = Email
} }
// Then try to get most recent user email from storage // Then try to get most recent user email from storage
if leEmail == "" { if leEmail == "" {
leEmail = cfg.mostRecentUserEmail() leEmail = cfg.mostRecentUserEmail()
cfg.Email = leEmail // save for next time
} }
if leEmail == "" && allowPrompts {
// Looks like there is no email address readily available, // Looks like there is no email address readily available,
// so we will have to ask the user if we can. // so we will have to ask the user if we can.
if leEmail == "" && userPresent { var err error
// evidently, no User data was present in storage; leEmail, err = cfg.promptUserForEmail()
// thus we must make a new User so that we can get
// the Terms of Service URL via our ACME client, phew!
user, err := cfg.newUser("")
if err != nil { if err != nil {
return "", err return err
}
cfg.Agreed = true
} }
// lower-casing the email is important for consistency
cfg.Email = strings.ToLower(leEmail)
return nil
}
// get the agreement URL func (cfg *Config) getAgreementURL() (string, error) {
agreementURL := agreementTestURL if agreementTestURL != "" {
if agreementURL == "" { return agreementTestURL, nil
// we call acme.NewClient directly because newACMEClient }
// would require that we already know the user's email
caURL := CA caURL := CA
if cfg.CA != "" { if cfg.CA != "" {
caURL = cfg.CA caURL = cfg.CA
} }
legoConfig := lego.NewConfig(user) response, err := http.Get(caURL)
legoConfig.CADirURL = caURL
legoConfig.UserAgent = UserAgent
tempClient, err := lego.NewClient(legoConfig)
if err != nil { if err != nil {
return "", fmt.Errorf("making ACME client to get ToS URL: %v", err) return "", err
} }
agreementURL = tempClient.GetToSURL() defer response.Body.Close()
var dir acme.Directory
err = json.NewDecoder(response.Body).Decode(&dir)
if err != nil {
return "", err
} }
return dir.Meta.TermsOfService, nil
}
func (cfg *Config) promptUserForEmail() (string, error) {
agreementURL, err := cfg.getAgreementURL()
if err != nil {
return "", fmt.Errorf("get Agreement URL: %v", err)
}
// prompt the user for an email address and terms agreement // prompt the user for an email address and terms agreement
reader := bufio.NewReader(stdin) reader := bufio.NewReader(stdin)
cfg.promptUserAgreement(agreementURL) cfg.promptUserAgreement(agreementURL)
fmt.Println("Please enter your email address to signify agreement and to be notified") fmt.Println("Please enter your email address to signify agreement and to be notified")
fmt.Println("in case of issues. You can leave it blank, but we don't recommend it.") fmt.Println("in case of issues. You can leave it blank, but we don't recommend it.")
fmt.Print(" Email address: ") fmt.Print(" Email address: ")
leEmail, err = reader.ReadString('\n') leEmail, err := reader.ReadString('\n')
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return "", fmt.Errorf("reading email address: %v", err) return "", fmt.Errorf("reading email address: %v", err)
} }
leEmail = strings.TrimSpace(leEmail) leEmail = strings.TrimSpace(leEmail)
cfg.Email = leEmail return leEmail, nil
cfg.Agreed = true
// save the new user to preserve this for next time
user.Email = leEmail
err = cfg.saveUser(user)
if err != nil {
return "", err
}
}
// lower-casing the email is important for consistency
return strings.ToLower(leEmail), nil
} }
// getUser loads the user with the given email from disk // getUser loads the user with the given email from disk
......
...@@ -138,7 +138,7 @@ ...@@ -138,7 +138,7 @@
"importpath": "github.com/mholt/certmagic", "importpath": "github.com/mholt/certmagic",
"repository": "https://github.com/mholt/certmagic", "repository": "https://github.com/mholt/certmagic",
"vcs": "git", "vcs": "git",
"revision": "01ffe8b3c7d611483ef936e90845329709721127", "revision": "c1d472b46046ee329c099086d689ada0c44d56b0",
"branch": "master", "branch": "master",
"notests": true "notests": true
}, },
......
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