2018-05-01 23:45:06 +00:00
|
|
|
package tlsconfig
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
2019-05-28 20:53:35 +00:00
|
|
|
"crypto/x509"
|
2018-05-01 23:45:06 +00:00
|
|
|
"fmt"
|
2019-05-28 20:53:35 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"net"
|
|
|
|
"runtime"
|
2018-05-01 23:45:06 +00:00
|
|
|
"sync"
|
|
|
|
|
|
|
|
"github.com/getsentry/raven-go"
|
2019-05-28 20:53:35 +00:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"gopkg.in/urfave/cli.v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
OriginCAPoolFlag = "origin-ca-pool"
|
|
|
|
CaCertFlag = "cacert"
|
2018-05-01 23:45:06 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// CertReloader can load and reload a TLS certificate from a particular filepath.
|
|
|
|
// Hooks into tls.Config's GetCertificate to allow a TLS server to update its certificate without restarting.
|
|
|
|
type CertReloader struct {
|
|
|
|
sync.Mutex
|
|
|
|
certificate *tls.Certificate
|
|
|
|
certPath string
|
|
|
|
keyPath string
|
|
|
|
}
|
|
|
|
|
2018-11-15 15:43:50 +00:00
|
|
|
// NewCertReloader makes a CertReloader. It loads the cert during initialization to make sure certPath and keyPath are valid
|
|
|
|
func NewCertReloader(certPath, keyPath string) (*CertReloader, error) {
|
2018-05-01 23:45:06 +00:00
|
|
|
cr := new(CertReloader)
|
2018-11-15 15:43:50 +00:00
|
|
|
cr.certPath = certPath
|
|
|
|
cr.keyPath = keyPath
|
|
|
|
if err := cr.LoadCert(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-05-01 23:45:06 +00:00
|
|
|
return cr, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cert returns the TLS certificate most recently read by the CertReloader.
|
|
|
|
func (cr *CertReloader) Cert(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
|
|
cr.Lock()
|
|
|
|
defer cr.Unlock()
|
|
|
|
return cr.certificate, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadCert loads a TLS certificate from the CertReloader's specified filepath.
|
|
|
|
// Call this after writing a new certificate to the disk (e.g. after renewing a certificate)
|
2018-11-15 15:43:50 +00:00
|
|
|
func (cr *CertReloader) LoadCert() error {
|
2018-05-01 23:45:06 +00:00
|
|
|
cr.Lock()
|
|
|
|
defer cr.Unlock()
|
|
|
|
|
|
|
|
cert, err := tls.LoadX509KeyPair(cr.certPath, cr.keyPath)
|
|
|
|
|
|
|
|
// Keep the old certificate if there's a problem reading the new one.
|
|
|
|
if err != nil {
|
|
|
|
raven.CaptureError(fmt.Errorf("Error parsing X509 key pair: %v", err), nil)
|
2018-11-15 15:43:50 +00:00
|
|
|
return err
|
2018-05-01 23:45:06 +00:00
|
|
|
}
|
|
|
|
cr.certificate = &cert
|
2018-11-15 15:43:50 +00:00
|
|
|
return nil
|
2018-05-01 23:45:06 +00:00
|
|
|
}
|
2019-05-28 20:53:35 +00:00
|
|
|
|
|
|
|
func LoadOriginCA(c *cli.Context, logger *logrus.Logger) (*x509.CertPool, error) {
|
|
|
|
var originCustomCAPool []byte
|
|
|
|
|
|
|
|
originCAPoolFilename := c.String(OriginCAPoolFlag)
|
|
|
|
if originCAPoolFilename != "" {
|
|
|
|
var err error
|
|
|
|
originCustomCAPool, err = ioutil.ReadFile(originCAPoolFilename)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, fmt.Sprintf("unable to read the file %s for --%s", originCAPoolFilename, OriginCAPoolFlag))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
originCertPool, err := loadOriginCertPool(originCustomCAPool, logger)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "error loading the certificate pool")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Windows users should be notified that they can use the flag
|
|
|
|
if runtime.GOOS == "windows" && originCAPoolFilename == "" {
|
|
|
|
logger.Infof("cloudflared does not support loading the system root certificate pool on Windows. Please use the --%s to specify it", OriginCAPoolFlag)
|
|
|
|
}
|
|
|
|
|
|
|
|
return originCertPool, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func LoadCustomCertPool(customCertFilename string) (*x509.CertPool, error) {
|
|
|
|
pool := x509.NewCertPool()
|
|
|
|
customCAPoolPEM, err := ioutil.ReadFile(customCertFilename)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, fmt.Sprintf("unable to read the file %s", customCertFilename))
|
|
|
|
}
|
|
|
|
if !pool.AppendCertsFromPEM(customCAPoolPEM) {
|
|
|
|
return nil, fmt.Errorf("error appending custom CA to cert pool")
|
|
|
|
}
|
|
|
|
return pool, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func CreateTunnelConfig(c *cli.Context) (*tls.Config, error) {
|
|
|
|
var rootCAs []string
|
|
|
|
if c.String(CaCertFlag) != "" {
|
|
|
|
rootCAs = append(rootCAs, c.String(CaCertFlag))
|
|
|
|
}
|
|
|
|
|
|
|
|
userConfig := &TLSParameters{RootCAs: rootCAs}
|
|
|
|
tlsConfig, err := GetConfig(userConfig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if tlsConfig.RootCAs == nil {
|
|
|
|
rootCAPool := x509.NewCertPool()
|
|
|
|
cfRootCA, err := GetCloudflareRootCA()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "could not append Cloudflare Root CAs to cloudflared certificate pool")
|
|
|
|
}
|
|
|
|
for _, cert := range cfRootCA {
|
|
|
|
rootCAPool.AddCert(cert)
|
|
|
|
}
|
|
|
|
tlsConfig.RootCAs = rootCAPool
|
|
|
|
tlsConfig.ServerName = "cftunnel.com"
|
|
|
|
} else if edgeAddrs := c.StringSlice("edge"); len(edgeAddrs) > 0 {
|
|
|
|
// Set for development environments and for testing specific origintunneld instances
|
|
|
|
tlsConfig.ServerName, _, _ = net.SplitHostPort(edgeAddrs[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
if tlsConfig.ServerName == "" && !tlsConfig.InsecureSkipVerify {
|
|
|
|
return nil, fmt.Errorf("either ServerName or InsecureSkipVerify must be specified in the tls.Config")
|
|
|
|
}
|
|
|
|
return tlsConfig, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func loadOriginCertPool(originCAPoolPEM []byte, logger *logrus.Logger) (*x509.CertPool, error) {
|
|
|
|
// Get the global pool
|
|
|
|
certPool, err := loadGlobalCertPool(logger)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then, add any custom origin CA pool the user may have passed
|
|
|
|
if originCAPoolPEM != nil {
|
|
|
|
if !certPool.AppendCertsFromPEM(originCAPoolPEM) {
|
|
|
|
logger.Warn("could not append the provided origin CA to the cloudflared certificate pool")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return certPool, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func loadGlobalCertPool(logger *logrus.Logger) (*x509.CertPool, error) {
|
|
|
|
// First, obtain the system certificate pool
|
|
|
|
certPool, err := x509.SystemCertPool()
|
|
|
|
if err != nil {
|
|
|
|
if runtime.GOOS != "windows" { // See https://github.com/golang/go/issues/16736
|
|
|
|
logger.WithError(err).Warn("error obtaining the system certificates")
|
|
|
|
}
|
|
|
|
certPool = x509.NewCertPool()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next, append the Cloudflare CAs into the system pool
|
|
|
|
cfRootCA, err := GetCloudflareRootCA()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "could not append Cloudflare Root CAs to cloudflared certificate pool")
|
|
|
|
}
|
|
|
|
for _, cert := range cfRootCA {
|
|
|
|
certPool.AddCert(cert)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, add the Hello certificate into the pool (since it's self-signed)
|
|
|
|
helloCert, err := GetHelloCertificateX509()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "could not append Hello server certificate to cloudflared certificate pool")
|
|
|
|
}
|
|
|
|
certPool.AddCert(helloCert)
|
|
|
|
|
|
|
|
return certPool, nil
|
|
|
|
}
|