From dff74f0823fca89636b9f3686757cc3f381c732e Mon Sep 17 00:00:00 2001 From: Marcelo Rydel Date: Thu, 7 Apr 2022 11:32:00 -0300 Subject: [PATCH] feat(ssl): enable mTLS certificates [EE-2617] (#6612) --- api/cli/defaults.go | 2 -- api/cli/defaults_windows.go | 2 -- api/cmd/portainer/log.go | 26 ++------------------------ api/cmd/portainer/main.go | 4 ++-- api/crypto/tls.go | 13 +------------ api/filesystem/filesystem.go | 28 +++++++++++++++++++++------- api/internal/ssl/ssl.go | 33 ++++++++++++++++++--------------- api/portainer.go | 1 + 8 files changed, 45 insertions(+), 64 deletions(-) diff --git a/api/cli/defaults.go b/api/cli/defaults.go index e7891cf3f..ca426cdad 100644 --- a/api/cli/defaults.go +++ b/api/cli/defaults.go @@ -18,8 +18,6 @@ const ( defaultHTTPDisabled = "false" defaultHTTPEnabled = "false" defaultSSL = "false" - defaultSSLCertPath = "/certs/portainer.crt" - defaultSSLKeyPath = "/certs/portainer.key" defaultBaseURL = "/" defaultSecretKeyName = "portainer" ) diff --git a/api/cli/defaults_windows.go b/api/cli/defaults_windows.go index 249082fb5..ed5996004 100644 --- a/api/cli/defaults_windows.go +++ b/api/cli/defaults_windows.go @@ -15,8 +15,6 @@ const ( defaultHTTPDisabled = "false" defaultHTTPEnabled = "false" defaultSSL = "false" - defaultSSLCertPath = "C:\\certs\\portainer.crt" - defaultSSLKeyPath = "C:\\certs\\portainer.key" defaultSnapshotInterval = "5m" defaultBaseURL = "/" defaultSecretKeyName = "portainer" diff --git a/api/cmd/portainer/log.go b/api/cmd/portainer/log.go index 570fb3554..88de199a2 100644 --- a/api/cmd/portainer/log.go +++ b/api/cmd/portainer/log.go @@ -1,41 +1,19 @@ package main import ( - "fmt" "log" - "strings" "github.com/sirupsen/logrus" ) -type portainerFormatter struct { - logrus.TextFormatter -} - -func (f *portainerFormatter) Format(entry *logrus.Entry) ([]byte, error) { - var levelColor int - switch entry.Level { - case logrus.DebugLevel, logrus.TraceLevel: - levelColor = 31 // gray - case logrus.WarnLevel: - levelColor = 33 // yellow - case logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel: - levelColor = 31 // red - default: - levelColor = 36 // blue - } - return []byte(fmt.Sprintf("\x1b[%dm%s\x1b[0m %s %s\n", levelColor, strings.ToUpper(entry.Level.String()), entry.Time.Format(f.TimestampFormat), entry.Message)), nil -} - func configureLogger() { logger := logrus.New() // logger is to implicitly substitute stdlib's log log.SetOutput(logger.Writer()) - formatter := &logrus.TextFormatter{DisableTimestamp: true, DisableLevelTruncation: true} - formatterLogrus := &portainerFormatter{logrus.TextFormatter{DisableTimestamp: false, DisableLevelTruncation: true, TimestampFormat: "2006/01/02 15:04:05", FullTimestamp: true}} + formatter := &logrus.TextFormatter{DisableTimestamp: false, DisableLevelTruncation: true} logger.SetFormatter(formatter) - logrus.SetFormatter(formatterLogrus) + logrus.SetFormatter(formatter) logger.SetLevel(logrus.DebugLevel) logrus.SetLevel(logrus.DebugLevel) diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index 61d89f8d6..a0e84aff2 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -208,7 +208,7 @@ func initGitService() portainer.GitService { return git.NewService() } -func initSSLService(addr, dataPath, certPath, keyPath string, fileService portainer.FileService, dataStore dataservices.DataStore, shutdownTrigger context.CancelFunc) (*ssl.Service, error) { +func initSSLService(addr, certPath, keyPath string, fileService portainer.FileService, dataStore dataservices.DataStore, shutdownTrigger context.CancelFunc) (*ssl.Service, error) { slices := strings.Split(addr, ":") host := slices[0] if host == "" { @@ -568,7 +568,7 @@ func buildServer(flags *portainer.CLIFlags) portainer.Server { cryptoService := initCryptoService() digitalSignatureService := initDigitalSignatureService() - sslService, err := initSSLService(*flags.AddrHTTPS, *flags.Data, *flags.SSLCert, *flags.SSLKey, fileService, dataStore, shutdownTrigger) + sslService, err := initSSLService(*flags.AddrHTTPS, *flags.SSLCert, *flags.SSLKey, fileService, dataStore, shutdownTrigger) if err != nil { logrus.Fatal(err) } diff --git a/api/crypto/tls.go b/api/crypto/tls.go index e46998898..2cb216593 100644 --- a/api/crypto/tls.go +++ b/api/crypto/tls.go @@ -9,18 +9,7 @@ import ( // CreateServerTLSConfiguration creates a basic tls.Config to be used by servers with recommended TLS settings func CreateServerTLSConfiguration() *tls.Config { return &tls.Config{ - MinVersion: tls.VersionTLS12, - CipherSuites: []uint16{ - tls.TLS_AES_128_GCM_SHA256, - tls.TLS_AES_256_GCM_SHA384, - tls.TLS_CHACHA20_POLY1305_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, - }, + MinVersion: tls.VersionTLS13, } } diff --git a/api/filesystem/filesystem.go b/api/filesystem/filesystem.go index 8e9226937..9cdbe2f5e 100644 --- a/api/filesystem/filesystem.go +++ b/api/filesystem/filesystem.go @@ -56,10 +56,12 @@ const ( TempPath = "tmp" // SSLCertPath represents the default ssl certificates path SSLCertPath = "certs" - // DefaultSSLCertFilename represents the default ssl certificate file name - DefaultSSLCertFilename = "cert.pem" - // DefaultSSLKeyFilename represents the default ssl key file name - DefaultSSLKeyFilename = "key.pem" + // SSLCertFilename represents the ssl certificate file name + SSLCertFilename = "cert.pem" + // SSLKeyFilename represents the ssl key file name + SSLKeyFilename = "key.pem" + // SSLCACertFilename represents the CA ssl certificate file name for mTLS + SSLCACertFilename = "ca-cert.pem" ) // ErrUndefinedTLSFileType represents an error returned on undefined TLS file type @@ -161,7 +163,7 @@ func (service *Service) Copy(fromFilePath string, toFilePath string, deleteIfExi } if !exists { - return errors.New("File doesn't exist") + return errors.New(fmt.Sprintf("File (%s) doesn't exist", fromFilePath)) } finput, err := os.Open(fromFilePath) @@ -580,8 +582,8 @@ func (service *Service) wrapFileStore(filepath string) string { } func defaultCertPathUnderFileStore() (string, string) { - certPath := JoinPaths(SSLCertPath, DefaultSSLCertFilename) - keyPath := JoinPaths(SSLCertPath, DefaultSSLKeyFilename) + certPath := JoinPaths(SSLCertPath, SSLCertFilename) + keyPath := JoinPaths(SSLCertPath, SSLKeyFilename) return certPath, keyPath } @@ -627,6 +629,18 @@ func (service *Service) CopySSLCertPair(certPath, keyPath string) (string, strin return defCertPath, defKeyPath, nil } +// CopySSLCACert copies the specified caCert pem file +func (service *Service) CopySSLCACert(caCertPath string) (string, error) { + toFilePath := service.wrapFileStore(JoinPaths(SSLCertPath, SSLCACertFilename)) + + err := service.Copy(caCertPath, toFilePath, true) + if err != nil { + return "", err + } + + return toFilePath, nil +} + // FileExists checks for the existence of the specified file. func FileExists(filePath string) (bool, error) { if _, err := os.Stat(filePath); err != nil { diff --git a/api/internal/ssl/ssl.go b/api/internal/ssl/ssl.go index ffaa32137..71fa315c2 100644 --- a/api/internal/ssl/ssl.go +++ b/api/internal/ssl/ssl.go @@ -8,6 +8,7 @@ import ( "time" "github.com/pkg/errors" + "github.com/portainer/libcrypto" portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/dataservices" @@ -32,8 +33,8 @@ func NewService(fileService portainer.FileService, dataStore dataservices.DataSt // Init initializes the service func (service *Service) Init(host, certPath, keyPath string) error { - pathSupplied := certPath != "" && keyPath != "" - if pathSupplied { + certSupplied := certPath != "" && keyPath != "" + if certSupplied { newCertPath, newKeyPath, err := service.fileService.CopySSLCertPair(certPath, keyPath) if err != nil { return errors.Wrap(err, "failed copying supplied certs") @@ -60,16 +61,24 @@ func (service *Service) Init(host, certPath, keyPath string) error { } } - // path not supplied and certificates doesn't exist - generate self signed + // path not supplied and certificates doesn't exist - generate self-signed certPath, keyPath = service.fileService.GetDefaultSSLCertsPath() - err = service.generateSelfSignedCertificates(host, certPath, keyPath) + err = generateSelfSignedCertificates(host, certPath, keyPath) if err != nil { return errors.Wrap(err, "failed generating self signed certs") } return service.cacheInfo(certPath, keyPath, true) +} +func generateSelfSignedCertificates(ip, certPath, keyPath string) error { + if ip == "" { + return errors.New("host can't be empty") + } + + log.Printf("[INFO] [internal,ssl] [message: no cert files found, generating self signed ssl certificates]") + return libcrypto.GenerateCertsForHost("localhost", ip, certPath, keyPath, time.Now().AddDate(5, 0, 0)) } // GetRawCertificate gets the raw certificate @@ -98,7 +107,10 @@ func (service *Service) SetCertificates(certData, keyData []byte) error { return err } - service.cacheInfo(certPath, keyPath, false) + err = service.cacheInfo(certPath, keyPath, false) + if err != nil { + return err + } service.shutdownTrigger() @@ -138,7 +150,7 @@ func (service *Service) cacheCertificate(certPath, keyPath string) error { return nil } -func (service *Service) cacheInfo(certPath, keyPath string, selfSigned bool) error { +func (service *Service) cacheInfo(certPath string, keyPath string, selfSigned bool) error { err := service.cacheCertificate(certPath, keyPath) if err != nil { return err @@ -160,12 +172,3 @@ func (service *Service) cacheInfo(certPath, keyPath string, selfSigned bool) err return nil } - -func (service *Service) generateSelfSignedCertificates(ip, certPath, keyPath string) error { - if ip == "" { - return errors.New("host can't be empty") - } - - log.Printf("[INFO] [internal,ssl] [message: no cert files found, generating self signed ssl certificates]") - return libcrypto.GenerateCertsForHost("localhost", ip, certPath, keyPath, time.Now().AddDate(5, 0, 0)) -} diff --git a/api/portainer.go b/api/portainer.go index 12e7da6eb..e2a64f814 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -1238,6 +1238,7 @@ type ( GetDefaultSSLCertsPath() (string, string) StoreSSLCertPair(cert, key []byte) (string, string, error) CopySSLCertPair(certPath, keyPath string) (string, string, error) + CopySSLCACert(caCertPath string) (string, error) StoreFDOProfileFileFromBytes(fdoProfileIdentifier string, data []byte) (string, error) }