github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/httpserver/tls.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package httpserver
     5  
     6  import (
     7  	"crypto/tls"
     8  	"net"
     9  	"strings"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/utils"
    13  	"golang.org/x/crypto/acme"
    14  	"golang.org/x/crypto/acme/autocert"
    15  
    16  	"github.com/juju/juju/state"
    17  )
    18  
    19  // NewTLSConfig returns the TLS configuration for the HTTP server to use
    20  // based on controller configuration stored in the state database.
    21  func NewTLSConfig(st *state.State, getCertificate func() *tls.Certificate) (*tls.Config, error) {
    22  	controllerConfig, err := st.ControllerConfig()
    23  	if err != nil {
    24  		return nil, errors.Trace(err)
    25  	}
    26  	return newTLSConfig(
    27  		controllerConfig.AutocertDNSName(),
    28  		controllerConfig.AutocertURL(),
    29  		st.AutocertCache(),
    30  		getCertificate,
    31  	), nil
    32  }
    33  
    34  func newTLSConfig(
    35  	autocertDNSName, autocertURL string,
    36  	autocertCache autocert.Cache,
    37  	getLocalCertificate func() *tls.Certificate,
    38  ) *tls.Config {
    39  	// localCertificate calls getLocalCertificate, returning the result
    40  	// and reporting whether it should be used to serve a connection
    41  	// addressed to the given server name.
    42  	localCertificate := func(serverName string) (*tls.Certificate, bool) {
    43  		cert := getLocalCertificate()
    44  		if net.ParseIP(serverName) != nil {
    45  			// IP address connections always use the local certificate.
    46  			return cert, true
    47  		}
    48  		if !strings.Contains(serverName, ".") {
    49  			// If the server name doesn't contain a period there's no
    50  			// way we can obtain a certificate for it.
    51  			// This applies to the common case where "juju-apiserver" is
    52  			// used as the server name.
    53  			return cert, true
    54  		}
    55  		// Perhaps the server name is explicitly mentioned by the server certificate.
    56  		for _, name := range cert.Leaf.DNSNames {
    57  			if name == serverName {
    58  				return cert, true
    59  			}
    60  		}
    61  		return cert, false
    62  	}
    63  
    64  	tlsConfig := utils.SecureTLSConfig()
    65  	if autocertDNSName == "" {
    66  		// No official DNS name, no certificate.
    67  		tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
    68  			cert, _ := localCertificate(clientHello.ServerName)
    69  			return cert, nil
    70  		}
    71  		return tlsConfig
    72  	}
    73  	m := autocert.Manager{
    74  		Prompt:     autocert.AcceptTOS,
    75  		Cache:      autocertCache,
    76  		HostPolicy: autocert.HostWhitelist(autocertDNSName),
    77  	}
    78  	if autocertURL != "" {
    79  		m.Client = &acme.Client{
    80  			DirectoryURL: autocertURL,
    81  		}
    82  	}
    83  	tlsConfig.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
    84  		logger.Infof("getting certificate for server name %q", clientHello.ServerName)
    85  		// Get the locally created certificate and whether it's appropriate
    86  		// for the SNI name. If not, we'll try to get an acme cert and
    87  		// fall back to the local certificate if that fails.
    88  		cert, shouldUse := localCertificate(clientHello.ServerName)
    89  		if shouldUse {
    90  			return cert, nil
    91  		}
    92  		acmeCert, err := m.GetCertificate(clientHello)
    93  		if err == nil {
    94  			return acmeCert, nil
    95  		}
    96  		logger.Errorf("cannot get autocert certificate for %q: %v", clientHello.ServerName, err)
    97  		return cert, nil
    98  	}
    99  	tlsConfig.NextProtos = []string{
   100  		"h2", "http/1.1", // Enable HTTP/2.
   101  		acme.ALPNProto, // Enable TLS-ALPN ACME challenges.
   102  	}
   103  	return tlsConfig
   104  }