Commit 45939820 authored by Matthew Holt's avatar Matthew Holt

letsencrypt: Major refactor of Activate(), fixes #474 and closes #397

Makes restarts cleaner and improves configuration usability related to the tls directive
parent 94100a7b
...@@ -22,58 +22,117 @@ import ( ...@@ -22,58 +22,117 @@ import (
"github.com/xenolf/lego/acme" "github.com/xenolf/lego/acme"
) )
func configureExisting(configs []server.Config) []server.Config { // Activate sets up TLS for each server config in configs
// Identify and configure any eligible hosts for which // as needed; this consists of acquiring and maintaining
// we already have certs and keys in storage from last time. // certificates and keys for qualifying configs and enabling
configLen := len(configs) // avoid infinite loop since this loop appends plaintext to the slice // OCSP stapling for all TLS-enabled configs.
for i := 0; i < configLen; i++ { //
if existingCertAndKey(configs[i].Host) && ConfigQualifies(configs, i) { // This function may prompt the user to provide an email
configs = autoConfigure(configs, i) // address if none is available through other means. It
} // prefers the email address specified in the config, but
} // if that is not available it will check the command line
return configs // argument. If absent, it will use the most recent email
} // address from last time. If there isn't one, the user
// will be prompted and shown SA link.
//
// Also note that calling this function activates asset
// management automatically, which keeps certificates
// renewed and OCSP stapling updated. This has the effect
// of causing restarts when assets are updated.
//
// Activate returns the updated list of configs, since
// some may have been appended, for example, to redirect
// plaintext HTTP requests to their HTTPS counterpart.
// This function only appends; it does not prepend or splice.
func Activate(configs []server.Config) ([]server.Config, error) {
// just in case previous caller forgot...
Deactivate()
// reset cached ocsp from any previous activations
ocspCache = make(map[*[]byte]*ocsp.Response)
// ObtainCertsAndConfigure obtains certificates for all qualifying configs. // pre-screen each config and earmark the ones that qualify for managed TLS
func ObtainCertsAndConfigure(configs []server.Config, optPort string) ([]server.Config, error) { MarkQualified(configs)
// Group configs by email address; only configs that are eligible
// for TLS management are included. We group by email so that we // place certificates and keys on disk
// can request certificates in batches with the same client. err := ObtainCerts(configs, "")
// Note: The return value is a map, and iteration over a map is
// not ordered. I don't think it will be a problem, but if an
// ordering problem arises, look at this carefully.
groupedConfigs, err := groupConfigsByEmail(configs)
if err != nil { if err != nil {
return configs, err return configs, err
} }
// obtain certificates for configs that need one, and reconfigure each // update TLS configurations
// config to use the certificates EnableTLS(configs)
finishedHosts := make(map[string]struct{})
for leEmail, cfgIndexes := range groupedConfigs { // enable OCSP stapling (this affects all TLS-enabled configs)
// make client to service this email address with CA server err = StapleOCSP(configs)
client, err := newClientPort(leEmail, optPort)
if err != nil { if err != nil {
return configs, errors.New("error creating client: " + err.Error()) return configs, err
}
// set up redirects
configs = MakePlaintextRedirects(configs)
// renew all relevant certificates that need renewal; TODO: handle errors
renewCertificates(configs, false)
// keep certificates renewed and OCSP stapling updated
go maintainAssets(configs, stopChan)
return configs, nil
}
// Deactivate cleans up long-term, in-memory resources
// allocated by calling Activate(). Essentially, it stops
// the asset maintainer from running, meaning that certificates
// will not be renewed, OCSP staples will not be updated, etc.
func Deactivate() (err error) {
defer func() {
if rec := recover(); rec != nil {
err = errors.New("already deactivated")
}
}()
close(stopChan)
stopChan = make(chan struct{})
return
}
// MarkQualified scans each config and, if it qualifies for managed
// TLS, it sets the Marked field of the TLSConfig to true.
func MarkQualified(configs []server.Config) {
for i := 0; i < len(configs); i++ {
if ConfigQualifies(configs[i]) {
configs[i].TLS.Managed = true
}
} }
}
// let's get free, trusted SSL certificates! // ObtainCerts obtains certificates for all these configs as long as a certificate does not
for _, idx := range cfgIndexes { // already exist on disk. It does not modify the configs at all; it only obtains and stores
hostname := configs[idx].Host // certificates and keys to the disk.
//
// TODO: Right now by potentially prompting about ToS error, we assume this function is only
// called at startup, but that is not always the case because it could be during a restart.
func ObtainCerts(configs []server.Config, optPort string) error {
groupedConfigs := groupConfigsByEmail(configs)
// prevent duplicate efforts, for example, when host is served on multiple ports for email, group := range groupedConfigs {
if _, ok := finishedHosts[hostname]; ok { client, err := newClientPort(email, optPort)
if err != nil {
return errors.New("error creating client: " + err.Error())
}
for _, cfg := range group {
if existingCertAndKey(cfg.Host) {
continue continue
} }
finishedHosts[hostname] = struct{}{}
Obtain: Obtain:
certificate, failures := client.ObtainCertificate([]string{hostname}, true) certificate, failures := client.ObtainCertificate([]string{cfg.Host}, true)
if len(failures) == 0 { if len(failures) == 0 {
// Success - immediately save the certificate resource // Success - immediately save the certificate resource
err := saveCertResource(certificate) err := saveCertResource(certificate)
if err != nil { if err != nil {
return configs, errors.New("error saving assets for " + hostname + ": " + err.Error()) return errors.New("error saving assets for " + cfg.Host + ": " + err.Error())
} }
} else { } else {
// Error - either try to fix it or report them it to the user and abort // Error - either try to fix it or report them it to the user and abort
...@@ -81,8 +140,9 @@ func ObtainCertsAndConfigure(configs []server.Config, optPort string) ([]server. ...@@ -81,8 +140,9 @@ func ObtainCertsAndConfigure(configs []server.Config, optPort string) ([]server.
var promptedForAgreement bool // only prompt user for agreement at most once var promptedForAgreement bool // only prompt user for agreement at most once
for errDomain, obtainErr := range failures { for errDomain, obtainErr := range failures {
if obtainErr != nil { // TODO: Double-check, will obtainErr ever be nil?
if tosErr, ok := obtainErr.(acme.TOSError); ok { if tosErr, ok := obtainErr.(acme.TOSError); ok {
// Terms of Service agreement error; we can probably deal with this
if !Agreed && !promptedForAgreement { if !Agreed && !promptedForAgreement {
Agreed = promptUserAgreement(tosErr.Detail, true) // TODO: Use latest URL Agreed = promptUserAgreement(tosErr.Detail, true) // TODO: Use latest URL
promptedForAgreement = true promptedForAgreement = true
...@@ -90,7 +150,7 @@ func ObtainCertsAndConfigure(configs []server.Config, optPort string) ([]server. ...@@ -90,7 +150,7 @@ func ObtainCertsAndConfigure(configs []server.Config, optPort string) ([]server.
if Agreed { if Agreed {
err := client.AgreeToTOS() err := client.AgreeToTOS()
if err != nil { if err != nil {
return configs, errors.New("error agreeing to updated terms: " + err.Error()) return errors.New("error agreeing to updated terms: " + err.Error())
} }
goto Obtain goto Obtain
} }
...@@ -99,91 +159,114 @@ func ObtainCertsAndConfigure(configs []server.Config, optPort string) ([]server. ...@@ -99,91 +159,114 @@ func ObtainCertsAndConfigure(configs []server.Config, optPort string) ([]server.
// If user did not agree or it was any other kind of error, just append to the list of errors // If user did not agree or it was any other kind of error, just append to the list of errors
errMsg += "[" + errDomain + "] failed to get certificate: " + obtainErr.Error() + "\n" errMsg += "[" + errDomain + "] failed to get certificate: " + obtainErr.Error() + "\n"
} }
}
return configs, errors.New(errMsg) return errors.New(errMsg)
} }
} }
// it all comes down to this: turning on TLS with all the new certs
for _, idx := range cfgIndexes {
configs = autoConfigure(configs, idx)
}
} }
return configs, nil return nil
} }
// Activate sets up TLS for each server config in configs // groupConfigsByEmail groups configs by the email address to be used by its
// as needed. It only skips the config if the cert and key // ACME client. It only includes configs that are marked as fully managed.
// are already provided, if plaintext http is explicitly // This is the function that may prompt for an email address.
// specified as the port, TLS is explicitly disabled, or func groupConfigsByEmail(configs []server.Config) map[string][]server.Config {
// the host looks like a loopback or wildcard address. initMap := make(map[string][]server.Config)
// for _, cfg := range configs {
// This function may prompt the user to provide an email if !cfg.TLS.Managed {
// address if none is available through other means. It continue
// prefers the email address specified in the config, but }
// if that is not available it will check the command line leEmail := getEmail(cfg)
// argument. If absent, it will use the most recent email initMap[leEmail] = append(initMap[leEmail], cfg)
// address from last time. If there isn't one, the user }
// will be prompted and shown SA link. return initMap
// }
// Also note that calling this function activates asset
// management automatically, which keeps certificates
// renewed and OCSP stapling updated. This has the effect
// of causing restarts when assets are updated.
//
// Activate returns the updated list of configs, since
// some may have been appended, for example, to redirect
// plaintext HTTP requests to their HTTPS counterpart.
// This function only appends; it does not prepend or splice.
func Activate(configs []server.Config) ([]server.Config, error) {
var err error
// just in case previous caller forgot...
Deactivate()
// reset cached ocsp from any previous activations // EnableTLS configures each config to use TLS according to default settings.
ocspCache = make(map[*[]byte]*ocsp.Response) // It will only change configs that are marked as managed, and assumes that
// certificates and keys are already on disk.
func EnableTLS(configs []server.Config) {
for i := 0; i < len(configs); i++ {
if !configs[i].TLS.Managed {
continue
}
configs[i].TLS.Enabled = true
configs[i].TLS.Certificate = storage.SiteCertFile(configs[i].Host)
configs[i].TLS.Key = storage.SiteKeyFile(configs[i].Host)
setup.SetDefaultTLSParams(&configs[i])
}
}
// configure configs for which we have an existing certificate // StapleOCSP staples OCSP responses to each config according to their certificate.
configs = configureExisting(configs) // This should work for any TLS-enabled config, not just Let's Encrypt ones.
func StapleOCSP(configs []server.Config) error {
for i := 0; i < len(configs); i++ {
if configs[i].TLS.Certificate == "" {
continue
}
// obtain certificates for configs which need one, and make them use them bundleBytes, err := ioutil.ReadFile(configs[i].TLS.Certificate)
configs, err = ObtainCertsAndConfigure(configs, "")
if err != nil { if err != nil {
return configs, err return errors.New("load certificate to staple ocsp: " + err.Error())
} }
// renew all relevant certificates that need renewal; TODO: handle errors ocspBytes, ocspResp, err := acme.GetOCSPForCert(bundleBytes)
renewCertificates(configs, false) if err == nil {
// TODO: We ignore the error if it exists because some certificates
// keep certificates renewed and OCSP stapling updated // may not have an issuer URL which we should ignore anyway, and
go maintainAssets(configs, stopChan) // sometimes we get syntax errors in the responses. To reproduce this
// behavior, start Caddy with an empty Caddyfile and -log stderr. Then
// add a host to the Caddyfile which requires a new LE certificate.
// Reload Caddy's config with SIGUSR1, and see the log report that it
// obtains the certificate, but then an error:
// getting ocsp: asn1: syntax error: sequence truncated
// But retrying the reload again sometimes solves the problem. It's flaky...
ocspCache[&bundleBytes] = ocspResp
if ocspResp.Status == ocsp.Good {
configs[i].TLS.OCSPStaple = ocspBytes
}
}
}
return nil
}
return configs, nil // hostHasOtherPort returns true if there is another config in the list with the same
// hostname that has port otherPort, or false otherwise. All the configs are checked
// against the hostname of allConfigs[thisConfigIdx].
func hostHasOtherPort(allConfigs []server.Config, thisConfigIdx int, otherPort string) bool {
for i, otherCfg := range allConfigs {
if i == thisConfigIdx {
continue // has to be a config OTHER than the one we're comparing against
}
if otherCfg.Host == allConfigs[thisConfigIdx].Host && otherCfg.Port == otherPort {
return true
}
}
return false
} }
// Deactivate cleans up long-term, in-memory resources // MakePlaintextRedirects sets up redirects from port 80 to the relevant HTTPS
// allocated by calling Activate(). Essentially, it stops // hosts. You must pass in all configs, not just configs that qualify, since
// the asset maintainer from running, meaning that certificates // we must know whether the same host already exists on port 80, and those would
// will not be renewed, OCSP staples will not be updated, etc. // not be in a list of configs that qualify for automatic HTTPS. This function will
func Deactivate() (err error) { // only set up redirects for configs that qualify. It returns the updated list of
defer func() { // all configs.
if rec := recover(); rec != nil { func MakePlaintextRedirects(allConfigs []server.Config) []server.Config {
err = errors.New("already deactivated") for i, cfg := range allConfigs {
if cfg.TLS.Managed &&
!hostHasOtherPort(allConfigs, i, "80") &&
(cfg.Port == "443" || !hostHasOtherPort(allConfigs, i, "443")) {
allConfigs = append(allConfigs, redirPlaintextHost(cfg))
} }
}() }
close(stopChan) return allConfigs
stopChan = make(chan struct{})
return
} }
// ConfigQualifies returns true if the config at cfgIndex (within allConfigs) // ConfigQualifies returns true if the config at cfgIndex (within allConfigs)
// qualifes for automatic LE activation. It does NOT check to see if a cert // qualifes for automatic LE activation. It does NOT check to see if a cert
// and key already exist for the config. // and key already exist for the config.
func ConfigQualifies(allConfigs []server.Config, cfgIndex int) bool { func ConfigQualifies(cfg server.Config) bool {
cfg := allConfigs[cfgIndex]
return cfg.TLS.Certificate == "" && // user could provide their own cert and key return cfg.TLS.Certificate == "" && // user could provide their own cert and key
cfg.TLS.Key == "" && cfg.TLS.Key == "" &&
...@@ -192,11 +275,8 @@ func ConfigQualifies(allConfigs []server.Config, cfgIndex int) bool { ...@@ -192,11 +275,8 @@ func ConfigQualifies(allConfigs []server.Config, cfgIndex int) bool {
cfg.Port != "80" && cfg.Port != "80" &&
cfg.TLS.LetsEncryptEmail != "off" && cfg.TLS.LetsEncryptEmail != "off" &&
// obviously we get can't certs for loopback or internal hosts // we get can't certs for some kinds of hostnames
HostQualifies(cfg.Host) && HostQualifies(cfg.Host)
// make sure another HTTPS version of this config doesn't exist in the list already
!otherHostHasScheme(allConfigs, cfgIndex, "https")
} }
// HostQualifies returns true if the hostname alone // HostQualifies returns true if the hostname alone
...@@ -208,33 +288,14 @@ func HostQualifies(hostname string) bool { ...@@ -208,33 +288,14 @@ func HostQualifies(hostname string) bool {
return hostname != "localhost" && return hostname != "localhost" &&
strings.TrimSpace(hostname) != "" && strings.TrimSpace(hostname) != "" &&
net.ParseIP(hostname) == nil && // cannot be an IP address, see: https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt net.ParseIP(hostname) == nil && // cannot be an IP address, see: https://community.letsencrypt.org/t/certificate-for-static-ip/84/2?u=mholt
// TODO: net.ParseIP also catches the two variants without brackets
hostname != "[::]" && // before parsing hostname != "[::]" && // before parsing
hostname != "::" && // after parsing hostname != "::" && // after parsing
hostname != "[::1]" && // before parsing hostname != "[::1]" && // before parsing
hostname != "::1" // after parsing hostname != "::1" // after parsing
} }
// groupConfigsByEmail groups configs by user email address. The returned map is
// a map of email address to the configs that are serviced under that account.
// If an email address is not available for an eligible config, the user will be
// prompted to provide one. The returned map contains pointers to the original
// server config values.
func groupConfigsByEmail(configs []server.Config) (map[string][]int, error) {
initMap := make(map[string][]int)
for i := 0; i < len(configs); i++ {
// filter out configs that we already have certs for and
// 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) {
continue
}
leEmail := getEmail(configs[i])
initMap[leEmail] = append(initMap[leEmail], i)
}
return initMap, nil
}
// existingCertAndKey returns true if the host has a certificate // existingCertAndKey returns true if the host has a certificate
// and private key in storage already, false otherwise. // and private key in storage already, false otherwise.
func existingCertAndKey(host string) bool { func existingCertAndKey(host string) bool {
...@@ -344,68 +405,10 @@ func saveCertResource(cert acme.CertificateResource) error { ...@@ -344,68 +405,10 @@ func saveCertResource(cert acme.CertificateResource) error {
return nil return nil
} }
// 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 {
ocspBytes, ocspResp, err := acme.GetOCSPForCert(bundleBytes)
ocspCache[&bundleBytes] = ocspResp
if err == nil && ocspResp.Status == ocsp.Good {
cfg.TLS.OCSPStaple = ocspBytes
}
}
cfg.TLS.Certificate = storage.SiteCertFile(cfg.Host)
cfg.TLS.Key = storage.SiteKeyFile(cfg.Host)
cfg.TLS.Enabled = true
setup.SetDefaultTLSParams(cfg)
if cfg.Port == "" {
cfg.Port = "443"
}
// 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.
// Also, the port 80 variant of this config is necessary for proxying challenge requests.
if !otherHostHasScheme(allConfigs, cfgIndex, "http") && cfg.Port != "80" && cfg.Scheme != "http" {
allConfigs = append(allConfigs, redirPlaintextHost(*cfg))
}
return allConfigs
}
// 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 == "http" {
scheme = "80"
} else if scheme == "https" {
scheme = "443"
}
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 && otherCfg.Port == scheme {
return true
}
}
return false
}
// 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 the "http" port (port 80). // to listen on port 80.
func redirPlaintextHost(cfg server.Config) server.Config { func redirPlaintextHost(cfg server.Config) server.Config {
toURL := "https://" + cfg.Host toURL := "https://" + cfg.Host
if cfg.Port != "443" && cfg.Port != "80" { if cfg.Port != "443" && cfg.Port != "80" {
......
...@@ -131,14 +131,13 @@ func getCertsForNewCaddyfile(newCaddyfile Input) error { ...@@ -131,14 +131,13 @@ func getCertsForNewCaddyfile(newCaddyfile Input) error {
return errors.New("loading Caddyfile: " + err.Error()) return errors.New("loading Caddyfile: " + err.Error())
} }
// TODO: Yuck, this is hacky. port 443 not set until letsencrypt is activated, so we change it here. // first mark the configs that are qualified for managed TLS
for i := range configs { letsencrypt.MarkQualified(configs)
if configs[i].Port == "" && letsencrypt.ConfigQualifies(configs, i) {
configs[i].Port = "443" // we must make sure port is set before we group by bind address
} letsencrypt.EnableTLS(configs)
}
// only get certs for configs that bind to an address we're already listening on // we only need to issue certs for hosts where we already have an active listener
groupings, err := arrangeBindings(configs) groupings, err := arrangeBindings(configs)
if err != nil { if err != nil {
return errors.New("arranging bindings: " + err.Error()) return errors.New("arranging bindings: " + err.Error())
...@@ -156,8 +155,8 @@ GroupLoop: ...@@ -156,8 +155,8 @@ GroupLoop:
} }
serversMu.Unlock() serversMu.Unlock()
// obtain certs for eligible configs; letsencrypt pkg will filter out the rest. // place certs on the disk
configs, err = letsencrypt.ObtainCertsAndConfigure(configsToSetup, letsencrypt.AlternatePort) err = letsencrypt.ObtainCerts(configsToSetup, letsencrypt.AlternatePort)
if err != nil { if err != nil {
return errors.New("obtaining certs: " + err.Error()) return errors.New("obtaining certs: " + err.Error())
} }
......
...@@ -11,12 +11,12 @@ import ( ...@@ -11,12 +11,12 @@ import (
// TLS sets up the TLS configuration (but does not activate Let's Encrypt; that is handled elsewhere). // TLS sets up the TLS configuration (but does not activate Let's Encrypt; that is handled elsewhere).
func TLS(c *Controller) (middleware.Middleware, error) { func TLS(c *Controller) (middleware.Middleware, error) {
if c.Scheme == "http" { if c.Scheme == "http" && c.Port != "80" {
c.TLS.Enabled = false c.TLS.Enabled = false
log.Printf("[WARNING] TLS disabled for %s://%s. To force TLS over the plaintext HTTP port, "+ log.Printf("[WARNING] TLS disabled for %s://%s. To force TLS over the plaintext HTTP port, "+
"specify port 80 explicitly (https://%s:80).", c.Scheme, c.Address(), c.Host) "specify port 80 explicitly (https://%s:80).", c.Scheme, c.Address(), c.Host)
} else { } else {
c.TLS.Enabled = true // assume this for now c.TLS.Enabled = true
} }
for c.Next() { for c.Next() {
...@@ -32,13 +32,6 @@ func TLS(c *Controller) (middleware.Middleware, error) { ...@@ -32,13 +32,6 @@ func TLS(c *Controller) (middleware.Middleware, error) {
case 2: case 2:
c.TLS.Certificate = args[0] c.TLS.Certificate = args[0]
c.TLS.Key = args[1] c.TLS.Key = args[1]
// manual HTTPS configuration without port specified should be
// served on the HTTPS port; that is what user would expect, and
// makes it consistent with how the letsencrypt package works.
if c.Port == "" {
c.Port = "443"
}
} }
// Optional block with extra parameters // Optional block with extra parameters
...@@ -86,8 +79,9 @@ func TLS(c *Controller) (middleware.Middleware, error) { ...@@ -86,8 +79,9 @@ func TLS(c *Controller) (middleware.Middleware, error) {
return nil, nil return nil, nil
} }
// SetDefaultTLSParams sets the default TLS cipher suites, protocol versions and server preferences // SetDefaultTLSParams sets the default TLS cipher suites, protocol versions,
// of a server.Config if they were not previously set. // and server preferences of a server.Config if they were not previously set
// (it does not overwrite; only fills in missing values).
func SetDefaultTLSParams(c *server.Config) { func SetDefaultTLSParams(c *server.Config) {
// If no ciphers provided, use all that Caddy supports for the protocol // If no ciphers provided, use all that Caddy supports for the protocol
if len(c.TLS.Ciphers) == 0 { if len(c.TLS.Ciphers) == 0 {
...@@ -107,6 +101,11 @@ func SetDefaultTLSParams(c *server.Config) { ...@@ -107,6 +101,11 @@ func SetDefaultTLSParams(c *server.Config) {
// Prefer server cipher suites // Prefer server cipher suites
c.TLS.PreferServerCipherSuites = true c.TLS.PreferServerCipherSuites = true
// Default TLS port is 443; only use if port is not manually specified
if c.Port == "" {
c.Port = "443"
}
} }
// Map of supported protocols // Map of supported protocols
......
...@@ -69,6 +69,7 @@ type TLSConfig struct { ...@@ -69,6 +69,7 @@ type TLSConfig struct {
Certificate string Certificate string
Key string Key string
LetsEncryptEmail string LetsEncryptEmail string
Managed bool // will be set to true if config qualifies for automatic, managed TLS
//DisableHTTPRedir bool // TODO: not a good idea - should we really allow it? //DisableHTTPRedir bool // TODO: not a good idea - should we really allow it?
OCSPStaple []byte OCSPStaple []byte
Ciphers []uint16 Ciphers []uint16
......
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