mirror of
https://github.com/grafana/grafana.git
synced 2024-11-27 03:11:01 -06:00
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
|
||
|
}
|