package ssl import ( "context" "crypto/tls" "os" "time" portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/dataservices" "github.com/portainer/portainer/pkg/libcrypto" "github.com/pkg/errors" "github.com/rs/zerolog/log" ) // Service represents a service to manage SSL certificates type Service struct { fileService portainer.FileService dataStore dataservices.DataStore rawCert *tls.Certificate shutdownTrigger context.CancelFunc } // NewService returns a pointer to a new Service func NewService(fileService portainer.FileService, dataStore dataservices.DataStore, shutdownTrigger context.CancelFunc) *Service { return &Service{ fileService: fileService, dataStore: dataStore, shutdownTrigger: shutdownTrigger, } } // Init initializes the service func (service *Service) Init(host, certPath, keyPath string) error { certSupplied := certPath != "" && keyPath != "" if certSupplied { newCertPath, newKeyPath, err := service.fileService.CopySSLCertPair(certPath, keyPath) if err != nil { return errors.Wrap(err, "failed copying supplied certs") } return service.cacheInfo(newCertPath, newKeyPath, false) } settings, err := service.GetSSLSettings() if err != nil { return errors.Wrap(err, "failed fetching SSL settings") } // certificates already exist if settings.CertPath != "" && settings.KeyPath != "" { err := service.cacheCertificate(settings.CertPath, settings.KeyPath) if err != nil && !os.IsNotExist(err) { return err } // continue if certs don't exist if err == nil { return nil } } // path not supplied and certificates doesn't exist - generate self-signed certPath, keyPath = service.fileService.GetDefaultSSLCertsPath() 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.Info().Msg("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 func (service *Service) GetRawCertificate() *tls.Certificate { return service.rawCert } // GetSSLSettings gets the certificate info func (service *Service) GetSSLSettings() (*portainer.SSLSettings, error) { return service.dataStore.SSLSettings().Settings() } // SetCertificates sets the certificates func (service *Service) SetCertificates(certData, keyData []byte) error { if len(certData) == 0 || len(keyData) == 0 { return errors.New("missing certificate files") } _, err := tls.X509KeyPair(certData, keyData) if err != nil { return err } certPath, keyPath, err := service.fileService.StoreSSLCertPair(certData, keyData) if err != nil { return err } err = service.cacheInfo(certPath, keyPath, false) if err != nil { return err } service.shutdownTrigger() return nil } func (service *Service) SetHTTPEnabled(httpEnabled bool) error { settings, err := service.dataStore.SSLSettings().Settings() if err != nil { return err } if settings.HTTPEnabled == httpEnabled { return nil } settings.HTTPEnabled = httpEnabled err = service.dataStore.SSLSettings().UpdateSettings(settings) if err != nil { return err } service.shutdownTrigger() return nil } func (service *Service) cacheCertificate(certPath, keyPath string) error { rawCert, err := tls.LoadX509KeyPair(certPath, keyPath) if err != nil { return err } service.rawCert = &rawCert return nil } func (service *Service) cacheInfo(certPath string, keyPath string, selfSigned bool) error { err := service.cacheCertificate(certPath, keyPath) if err != nil { return err } settings, err := service.dataStore.SSLSettings().Settings() if err != nil { return err } settings.CertPath = certPath settings.KeyPath = keyPath settings.SelfSigned = selfSigned return service.dataStore.SSLSettings().UpdateSettings(settings) }