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"
"runtime"
2018-05-01 23:45:06 +00:00
"sync"
2022-12-25 04:44:15 +00:00
"github.com/getsentry/sentry-go"
2019-05-28 20:53:35 +00:00
"github.com/pkg/errors"
2020-11-25 06:55:13 +00:00
"github.com/rs/zerolog"
2020-08-05 10:49:53 +00:00
"github.com/urfave/cli/v2"
2019-05-28 20:53:35 +00:00
)
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.
2022-09-19 11:47:18 +00:00
// This method works as a direct utility method for tls.Config#Cert.
2018-05-01 23:45:06 +00:00
func ( cr * CertReloader ) Cert ( clientHello * tls . ClientHelloInfo ) ( * tls . Certificate , error ) {
cr . Lock ( )
defer cr . Unlock ( )
return cr . certificate , nil
}
2022-09-19 11:47:18 +00:00
// ClientCert returns the TLS certificate most recently read by the CertReloader.
// This method works as a direct utility method for tls.Config#ClientCert.
func ( cr * CertReloader ) ClientCert ( certRequestInfo * tls . CertificateRequestInfo ) ( * tls . Certificate , error ) {
cr . Lock ( )
defer cr . Unlock ( )
return cr . certificate , nil
}
2018-05-01 23:45:06 +00:00
// 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 {
2022-12-25 04:44:15 +00:00
sentry . CaptureException ( fmt . Errorf ( "Error parsing X509 key pair: %v" , err ) )
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
2020-11-25 06:55:13 +00:00
func LoadOriginCA ( originCAPoolFilename string , log * zerolog . Logger ) ( * x509 . CertPool , error ) {
2019-05-28 20:53:35 +00:00
var originCustomCAPool [ ] byte
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 ) )
}
}
2020-11-25 06:55:13 +00:00
originCertPool , err := loadOriginCertPool ( originCustomCAPool , log )
2019-05-28 20:53:35 +00:00
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 == "" {
2020-12-31 16:10:47 +00:00
log . Info ( ) . Msgf ( "cloudflared does not support loading the system root certificate pool on Windows. Please use --%s <PATH> to specify the path to the certificate pool" , OriginCAPoolFlag )
2019-05-28 20:53:35 +00:00
}
return originCertPool , nil
}
2019-06-21 00:29:18 +00:00
func LoadCustomOriginCA ( originCAFilename string ) ( * x509 . CertPool , error ) {
// First, obtain the system certificate pool
certPool , err := x509 . SystemCertPool ( )
2019-05-28 20:53:35 +00:00
if err != nil {
2019-06-21 00:29:18 +00:00
certPool = x509 . NewCertPool ( )
2019-05-28 20:53:35 +00:00
}
2019-06-21 00:29:18 +00:00
// 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 )
}
if originCAFilename == "" {
return certPool , nil
}
customOriginCA , err := ioutil . ReadFile ( originCAFilename )
if err != nil {
return nil , errors . Wrap ( err , fmt . Sprintf ( "unable to read the file %s" , originCAFilename ) )
}
if ! certPool . AppendCertsFromPEM ( customOriginCA ) {
2019-05-28 20:53:35 +00:00
return nil , fmt . Errorf ( "error appending custom CA to cert pool" )
}
2019-06-21 00:29:18 +00:00
return certPool , nil
2019-05-28 20:53:35 +00:00
}
2020-10-08 10:12:26 +00:00
func CreateTunnelConfig ( c * cli . Context , serverName string ) ( * tls . Config , error ) {
2019-05-28 20:53:35 +00:00
var rootCAs [ ] string
if c . String ( CaCertFlag ) != "" {
rootCAs = append ( rootCAs , c . String ( CaCertFlag ) )
}
2020-09-11 22:02:34 +00:00
userConfig := & TLSParameters { RootCAs : rootCAs , ServerName : serverName }
2019-05-28 20:53:35 +00:00
tlsConfig , err := GetConfig ( userConfig )
if err != nil {
return nil , err
}
if tlsConfig . RootCAs == nil {
2022-09-19 11:34:26 +00:00
rootCAPool , err := x509 . SystemCertPool ( )
if err != nil {
return nil , errors . Wrap ( err , "unable to get x509 system cert pool" )
}
2019-05-28 20:53:35 +00:00
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
}
if tlsConfig . ServerName == "" && ! tlsConfig . InsecureSkipVerify {
return nil , fmt . Errorf ( "either ServerName or InsecureSkipVerify must be specified in the tls.Config" )
}
return tlsConfig , nil
}
2020-11-25 06:55:13 +00:00
func loadOriginCertPool ( originCAPoolPEM [ ] byte , log * zerolog . Logger ) ( * x509 . CertPool , error ) {
2019-05-28 20:53:35 +00:00
// Get the global pool
2020-11-25 06:55:13 +00:00
certPool , err := loadGlobalCertPool ( log )
2019-05-28 20:53:35 +00:00
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 ) {
2020-11-25 06:55:13 +00:00
log . Info ( ) . Msg ( "could not append the provided origin CA to the cloudflared certificate pool" )
2019-05-28 20:53:35 +00:00
}
}
return certPool , nil
}
2020-11-25 06:55:13 +00:00
func loadGlobalCertPool ( log * zerolog . Logger ) ( * x509 . CertPool , error ) {
2019-05-28 20:53:35 +00:00
// 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
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msg ( "error obtaining the system certificates" )
2019-05-28 20:53:35 +00:00
}
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
}