mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			229 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			229 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Go
		
	
	
	
| package postgres
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"sync"
 | |
| 
 | |
| 	"github.com/grafana/grafana/pkg/infra/fs"
 | |
| 	"github.com/grafana/grafana/pkg/infra/log"
 | |
| 	"github.com/grafana/grafana/pkg/models"
 | |
| )
 | |
| 
 | |
| var validateCertFunc = validateCertFilePaths
 | |
| var writeCertFileFunc = writeCertFile
 | |
| 
 | |
| type tlsSettingsProvider interface {
 | |
| 	getTLSSettings(datasource *models.DataSource) (tlsSettings, error)
 | |
| }
 | |
| 
 | |
| type datasourceCacheManager struct {
 | |
| 	locker *locker
 | |
| 	cache  sync.Map
 | |
| }
 | |
| 
 | |
| type tlsManager struct {
 | |
| 	logger          log.Logger
 | |
| 	dsCacheInstance datasourceCacheManager
 | |
| 	dataPath        string
 | |
| }
 | |
| 
 | |
| func newTLSManager(logger log.Logger, dataPath string) tlsSettingsProvider {
 | |
| 	return &tlsManager{
 | |
| 		logger:          logger,
 | |
| 		dataPath:        dataPath,
 | |
| 		dsCacheInstance: datasourceCacheManager{locker: newLocker()},
 | |
| 	}
 | |
| }
 | |
| 
 | |
| type tlsSettings struct {
 | |
| 	Mode                string
 | |
| 	ConfigurationMethod string
 | |
| 	RootCertFile        string
 | |
| 	CertFile            string
 | |
| 	CertKeyFile         string
 | |
| }
 | |
| 
 | |
| func (m *tlsManager) getTLSSettings(datasource *models.DataSource) (tlsSettings, error) {
 | |
| 	tlsMode := strings.TrimSpace(strings.ToLower(datasource.JsonData.Get("sslmode").MustString("verify-full")))
 | |
| 	isTLSDisabled := tlsMode == "disable"
 | |
| 
 | |
| 	settings := tlsSettings{}
 | |
| 	settings.Mode = tlsMode
 | |
| 
 | |
| 	if isTLSDisabled {
 | |
| 		m.logger.Debug("Postgres TLS/SSL is disabled")
 | |
| 		return settings, nil
 | |
| 	}
 | |
| 
 | |
| 	m.logger.Debug("Postgres TLS/SSL is enabled", "tlsMode", tlsMode)
 | |
| 
 | |
| 	settings.ConfigurationMethod = strings.TrimSpace(
 | |
| 		strings.ToLower(datasource.JsonData.Get("tlsConfigurationMethod").MustString("file-path")))
 | |
| 
 | |
| 	if settings.ConfigurationMethod == "file-content" {
 | |
| 		if err := m.writeCertFiles(datasource, &settings); err != nil {
 | |
| 			return settings, err
 | |
| 		}
 | |
| 	} else {
 | |
| 		settings.RootCertFile = datasource.JsonData.Get("sslRootCertFile").MustString("")
 | |
| 		settings.CertFile = datasource.JsonData.Get("sslCertFile").MustString("")
 | |
| 		settings.CertKeyFile = datasource.JsonData.Get("sslKeyFile").MustString("")
 | |
| 		if err := validateCertFunc(settings.RootCertFile, settings.CertFile, settings.CertKeyFile); err != nil {
 | |
| 			return settings, err
 | |
| 		}
 | |
| 	}
 | |
| 	return settings, nil
 | |
| }
 | |
| 
 | |
| type certFileType int
 | |
| 
 | |
| const (
 | |
| 	rootCert = iota
 | |
| 	clientCert
 | |
| 	clientKey
 | |
| )
 | |
| 
 | |
| func (t certFileType) String() string {
 | |
| 	switch t {
 | |
| 	case rootCert:
 | |
| 		return "root certificate"
 | |
| 	case clientCert:
 | |
| 		return "client certificate"
 | |
| 	case clientKey:
 | |
| 		return "client key"
 | |
| 	default:
 | |
| 		panic(fmt.Sprintf("Unrecognized certFileType %d", t))
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func getFileName(dataDir string, fileType certFileType) string {
 | |
| 	var filename string
 | |
| 	switch fileType {
 | |
| 	case rootCert:
 | |
| 		filename = "root.crt"
 | |
| 	case clientCert:
 | |
| 		filename = "client.crt"
 | |
| 	case clientKey:
 | |
| 		filename = "client.key"
 | |
| 	default:
 | |
| 		panic(fmt.Sprintf("unrecognized certFileType %s", fileType.String()))
 | |
| 	}
 | |
| 	generatedFilePath := filepath.Join(dataDir, filename)
 | |
| 	return generatedFilePath
 | |
| }
 | |
| 
 | |
| // writeCertFile writes a certificate file.
 | |
| func writeCertFile(
 | |
| 	ds *models.DataSource, logger log.Logger, fileContent string, generatedFilePath string) error {
 | |
| 	fileContent = strings.TrimSpace(fileContent)
 | |
| 	if fileContent != "" {
 | |
| 		logger.Debug("Writing cert file", "path", generatedFilePath)
 | |
| 		if err := ioutil.WriteFile(generatedFilePath, []byte(fileContent), 0600); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		// Make sure the file has the permissions expected by the Postgresql driver, otherwise it will bail
 | |
| 		if err := os.Chmod(generatedFilePath, 0600); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	logger.Debug("Deleting cert file since no content is provided", "path", generatedFilePath)
 | |
| 	exists, err := fs.Exists(generatedFilePath)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if exists {
 | |
| 		if err := os.Remove(generatedFilePath); err != nil {
 | |
| 			return fmt.Errorf("failed to remove %q: %w", generatedFilePath, err)
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (m *tlsManager) writeCertFiles(ds *models.DataSource, settings *tlsSettings) error {
 | |
| 	m.logger.Debug("Writing TLS certificate files to disk")
 | |
| 	decrypted := ds.DecryptedValues()
 | |
| 	tlsRootCert := decrypted["tlsCACert"]
 | |
| 	tlsClientCert := decrypted["tlsClientCert"]
 | |
| 	tlsClientKey := decrypted["tlsClientKey"]
 | |
| 
 | |
| 	if tlsRootCert == "" && tlsClientCert == "" && tlsClientKey == "" {
 | |
| 		m.logger.Debug("No TLS/SSL certificates provided")
 | |
| 	}
 | |
| 
 | |
| 	// Calculate all files path
 | |
| 	workDir := filepath.Join(m.dataPath, "tls", ds.Uid+"generatedTLSCerts")
 | |
| 	settings.RootCertFile = getFileName(workDir, rootCert)
 | |
| 	settings.CertFile = getFileName(workDir, clientCert)
 | |
| 	settings.CertKeyFile = getFileName(workDir, clientKey)
 | |
| 
 | |
| 	// Find datasource in the cache, if found, skip writing files
 | |
| 	cacheKey := strconv.Itoa(int(ds.Id))
 | |
| 	m.dsCacheInstance.locker.RLock(cacheKey)
 | |
| 	item, ok := m.dsCacheInstance.cache.Load(cacheKey)
 | |
| 	m.dsCacheInstance.locker.RUnlock(cacheKey)
 | |
| 	if ok {
 | |
| 		if item.(int) == ds.Version {
 | |
| 			return nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	m.dsCacheInstance.locker.Lock(cacheKey)
 | |
| 	defer m.dsCacheInstance.locker.Unlock(cacheKey)
 | |
| 
 | |
| 	item, ok = m.dsCacheInstance.cache.Load(cacheKey)
 | |
| 	if ok {
 | |
| 		if item.(int) == ds.Version {
 | |
| 			return nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// Write certification directory and files
 | |
| 	exists, err := fs.Exists(workDir)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if !exists {
 | |
| 		if err := os.MkdirAll(workDir, 0700); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if err = writeCertFileFunc(ds, m.logger, tlsRootCert, settings.RootCertFile); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err = writeCertFileFunc(ds, m.logger, tlsClientCert, settings.CertFile); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err = writeCertFileFunc(ds, m.logger, tlsClientKey, settings.CertKeyFile); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// Update datasource cache
 | |
| 	m.dsCacheInstance.cache.Store(cacheKey, ds.Version)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // validateCertFilePaths validates configured certificate file paths.
 | |
| func validateCertFilePaths(rootCert, clientCert, clientKey string) error {
 | |
| 	for _, fpath := range []string{rootCert, clientCert, clientKey} {
 | |
| 		if fpath == "" {
 | |
| 			continue
 | |
| 		}
 | |
| 		exists, err := fs.Exists(fpath)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		if !exists {
 | |
| 			return fmt.Errorf("certificate file %q doesn't exist", fpath)
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 |