mirror of
https://github.com/grafana/grafana.git
synced 2025-07-31 04:22:13 +08:00
148 lines
4.5 KiB
Go
148 lines
4.5 KiB
Go
package tls
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
|
|
"github.com/grafana/grafana/pkg/tsdb/sqleng"
|
|
)
|
|
|
|
// we support 4 postgres tls modes:
|
|
// disable - no tls
|
|
// require - use tls
|
|
// verify-ca - use tls, verify root cert but not the hostname
|
|
// verify-full - use tls, verify root cert
|
|
// (for all the options except `disable`, you can optionally use client certificates)
|
|
|
|
var errNoRootCert = errors.New("tls: missing root certificate")
|
|
|
|
func getTLSConfigRequire(certs *Certs) (*tls.Config, error) {
|
|
// we may have a client-cert, we do not have a root-cert
|
|
|
|
// see https://www.postgresql.org/docs/12/libpq-ssl.html ,
|
|
// mode=require + provided root-cert should behave as mode=verify-ca
|
|
if certs.rootCerts != nil {
|
|
return getTLSConfigVerifyCA(certs)
|
|
}
|
|
|
|
return &tls.Config{
|
|
InsecureSkipVerify: true, // we do not verify the root cert
|
|
Certificates: certs.clientCerts,
|
|
}, nil
|
|
}
|
|
|
|
// to implement the verify-ca mode, we need to do this:
|
|
// - for the root certificate
|
|
// - verify that the certificate we receive from the server is trusted,
|
|
// meaning it relates to our root certificate
|
|
// - we DO NOT verify that the hostname of the database matches
|
|
// the hostname in the certificate
|
|
//
|
|
// the problem is, `go“ does not offer such an option.
|
|
// by default, it will verify both things.
|
|
//
|
|
// so what we do is:
|
|
// - we turn off the default-verification with `InsecureSkipVerify`
|
|
// - we implement our own verification using `VerifyConnection`
|
|
//
|
|
// extra info about this:
|
|
// - there is a rejected feature-request about this at https://github.com/golang/go/issues/21971
|
|
// - the recommended workaround is based on VerifyPeerCertificate
|
|
// - there is even example code at https://github.com/golang/go/commit/29cfb4d3c3a97b6f426d1b899234da905be699aa
|
|
// - but later the example code was changed to use VerifyConnection instead:
|
|
// https://github.com/golang/go/commit/7eb5941b95a588a23f18fa4c22fe42ff0119c311
|
|
//
|
|
// a verifyConnection example is at https://pkg.go.dev/crypto/tls#example-Config-VerifyConnection .
|
|
//
|
|
// this is how the `pgx` library handles verify-ca:
|
|
//
|
|
// https://github.com/jackc/pgx/blob/5c63f646f820ca9696fc3515c1caf2a557d562e5/pgconn/config.go#L657-L690
|
|
// (unfortunately pgx only handles this for certificate-provided-as-path, so we cannot rely on it)
|
|
func getTLSConfigVerifyCA(certs *Certs) (*tls.Config, error) {
|
|
// we must have a root certificate
|
|
if certs.rootCerts == nil {
|
|
return nil, errNoRootCert
|
|
}
|
|
|
|
conf := tls.Config{
|
|
Certificates: certs.clientCerts,
|
|
InsecureSkipVerify: true, // we turn off the default-verification, we'll do VerifyConnection instead
|
|
VerifyConnection: func(state tls.ConnectionState) error {
|
|
// we add all the certificates to the pool, we skip the first cert.
|
|
intermediates := x509.NewCertPool()
|
|
for _, c := range state.PeerCertificates[1:] {
|
|
intermediates.AddCert(c)
|
|
}
|
|
|
|
opts := x509.VerifyOptions{
|
|
Roots: certs.rootCerts,
|
|
Intermediates: intermediates,
|
|
}
|
|
|
|
// we call `Verify()` on the first cert (that we skipped previously)
|
|
_, err := state.PeerCertificates[0].Verify(opts)
|
|
return err
|
|
},
|
|
RootCAs: certs.rootCerts,
|
|
}
|
|
|
|
return &conf, nil
|
|
}
|
|
|
|
func getTLSConfigVerifyFull(certs *Certs, serverName string) (*tls.Config, error) {
|
|
// we must have a root certificate
|
|
if certs.rootCerts == nil {
|
|
return nil, errNoRootCert
|
|
}
|
|
|
|
conf := tls.Config{
|
|
Certificates: certs.clientCerts,
|
|
ServerName: serverName,
|
|
RootCAs: certs.rootCerts,
|
|
}
|
|
|
|
return &conf, nil
|
|
}
|
|
|
|
func IsTLSEnabled(dsInfo sqleng.DataSourceInfo) bool {
|
|
mode := dsInfo.JsonData.Mode
|
|
return mode != "disable"
|
|
}
|
|
|
|
// returns `nil` if tls is disabled
|
|
func GetTLSConfig(dsInfo sqleng.DataSourceInfo, readFile ReadFileFunc, serverName string) (*tls.Config, error) {
|
|
mode := dsInfo.JsonData.Mode
|
|
// we need to special-case the no-tls-mode
|
|
if mode == "disable" {
|
|
return nil, nil
|
|
}
|
|
|
|
// for all the remaining cases we need to load
|
|
// both the root-cert if exists, and the client-cert if exists.
|
|
certBytes, err := loadCertificateBytes(dsInfo, readFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
certs, err := createCertificates(certBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
switch mode {
|
|
// `disable` already handled
|
|
case "":
|
|
// for backward-compatibility reasons this is the same as `require`
|
|
return getTLSConfigRequire(certs)
|
|
case "require":
|
|
return getTLSConfigRequire(certs)
|
|
case "verify-ca":
|
|
return getTLSConfigVerifyCA(certs)
|
|
case "verify-full":
|
|
return getTLSConfigVerifyFull(certs, serverName)
|
|
default:
|
|
return nil, errors.New("tls: invalid mode " + mode)
|
|
}
|
|
}
|