| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | package postgres | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io/ioutil" | 
					
						
							|  |  |  | 	"os" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/infra/fs" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/infra/log" | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/tsdb/sqleng" | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | var validateCertFunc = validateCertFilePaths | 
					
						
							|  |  |  | var writeCertFileFunc = writeCertFile | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | type certFileType int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	rootCert = iota | 
					
						
							|  |  |  | 	clientCert | 
					
						
							|  |  |  | 	clientKey | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | type tlsSettingsProvider interface { | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 	getTLSSettings(dsInfo sqleng.DataSourceInfo) (tlsSettings, error) | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | func (m *tlsManager) getTLSSettings(dsInfo sqleng.DataSourceInfo) (tlsSettings, error) { | 
					
						
							|  |  |  | 	tlsconfig := tlsSettings{ | 
					
						
							|  |  |  | 		Mode: dsInfo.JsonData.Mode, | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 	isTLSDisabled := (tlsconfig.Mode == "disable") | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if isTLSDisabled { | 
					
						
							|  |  |  | 		m.logger.Debug("Postgres TLS/SSL is disabled") | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 		return tlsconfig, nil | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 	m.logger.Debug("Postgres TLS/SSL is enabled", "tlsMode", tlsconfig.Mode) | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 	tlsconfig.ConfigurationMethod = dsInfo.JsonData.ConfigurationMethod | 
					
						
							|  |  |  | 	tlsconfig.RootCertFile = dsInfo.JsonData.RootCertFile | 
					
						
							|  |  |  | 	tlsconfig.CertFile = dsInfo.JsonData.CertFile | 
					
						
							|  |  |  | 	tlsconfig.CertKeyFile = dsInfo.JsonData.CertKeyFile | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 	if tlsconfig.ConfigurationMethod == "file-content" { | 
					
						
							|  |  |  | 		if err := m.writeCertFiles(dsInfo, &tlsconfig); err != nil { | 
					
						
							|  |  |  | 			return tlsconfig, err | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 		if err := validateCertFunc(tlsconfig.RootCertFile, tlsconfig.CertFile, tlsconfig.CertKeyFile); err != nil { | 
					
						
							|  |  |  | 			return tlsconfig, err | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 	return tlsconfig, nil | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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.
 | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | func writeCertFile(logger log.Logger, fileContent string, generatedFilePath string) error { | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 	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 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | func (m *tlsManager) writeCertFiles(dsInfo sqleng.DataSourceInfo, tlsconfig *tlsSettings) error { | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 	m.logger.Debug("Writing TLS certificate files to disk") | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 	tlsRootCert := dsInfo.DecryptedSecureJSONData["tlsCACert"] | 
					
						
							|  |  |  | 	tlsClientCert := dsInfo.DecryptedSecureJSONData["tlsClientCert"] | 
					
						
							|  |  |  | 	tlsClientKey := dsInfo.DecryptedSecureJSONData["tlsClientKey"] | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 	if tlsRootCert == "" && tlsClientCert == "" && tlsClientKey == "" { | 
					
						
							|  |  |  | 		m.logger.Debug("No TLS/SSL certificates provided") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Calculate all files path
 | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 	workDir := filepath.Join(m.dataPath, "tls", dsInfo.UID+"generatedTLSCerts") | 
					
						
							|  |  |  | 	tlsconfig.RootCertFile = getFileName(workDir, rootCert) | 
					
						
							|  |  |  | 	tlsconfig.CertFile = getFileName(workDir, clientCert) | 
					
						
							|  |  |  | 	tlsconfig.CertKeyFile = getFileName(workDir, clientKey) | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Find datasource in the cache, if found, skip writing files
 | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 	cacheKey := strconv.Itoa(int(dsInfo.ID)) | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 	m.dsCacheInstance.locker.RLock(cacheKey) | 
					
						
							|  |  |  | 	item, ok := m.dsCacheInstance.cache.Load(cacheKey) | 
					
						
							|  |  |  | 	m.dsCacheInstance.locker.RUnlock(cacheKey) | 
					
						
							|  |  |  | 	if ok { | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 		if !item.(time.Time).Before(dsInfo.Updated) { | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	m.dsCacheInstance.locker.Lock(cacheKey) | 
					
						
							|  |  |  | 	defer m.dsCacheInstance.locker.Unlock(cacheKey) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	item, ok = m.dsCacheInstance.cache.Load(cacheKey) | 
					
						
							|  |  |  | 	if ok { | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 		if !item.(time.Time).Before(dsInfo.Updated) { | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 			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 | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 	if err = writeCertFileFunc(m.logger, tlsRootCert, tlsconfig.RootCertFile); err != nil { | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 	if err = writeCertFileFunc(m.logger, tlsClientCert, tlsconfig.CertFile); err != nil { | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 	if err = writeCertFileFunc(m.logger, tlsClientKey, tlsconfig.CertKeyFile); err != nil { | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Update datasource cache
 | 
					
						
							| 
									
										
										
										
											2021-09-07 15:35:37 +08:00
										 |  |  | 	m.dsCacheInstance.cache.Store(cacheKey, dsInfo.Updated) | 
					
						
							| 
									
										
										
										
											2021-02-24 05:10:55 +08:00
										 |  |  | 	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 | 
					
						
							|  |  |  | } |