Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate TLS config for AcraConnector and DB #419

Merged
merged 12 commits into from Sep 28, 2020
11 changes: 11 additions & 0 deletions CHANGELOG_DEV.md
@@ -1,3 +1,14 @@
## 0.85.0 - 2020-09-28

### Added

- More specific TLS configuration options that allow to configure separate TLS settings between client app and AcraServer, and between AcraServer and database:
- AcraServer certificate: `tls_client_cert` and `tls_database_cert` (override `tls_cert`)
- AcraServer key: `tls_client_key` and `tls_database_key` (override `tls_key`)
- CA certificate path: `tls_client_ca` and `tls_database_ca` (override `tls_ca`)
- TLS authentication: `tls_client_auth` and `tls_database_auth` (override `tls_auth`)
- Renamed database SNI option: `tls_db_sni` => `tls_database_sni`

## 0.85.0 - 2020-04-02
### Added
- Support of RHEL >= 7
Expand Down
67 changes: 57 additions & 10 deletions cmd/acra-server/acra-server.go
Expand Up @@ -73,6 +73,8 @@ const (
// defaultConfigPath relative path to config which will be parsed as default
var defaultConfigPath = utils.GetConfigPathByName(ServiceName)

const tlsAuthNotSet = -1

func main() {
loggingFormat := flag.String("logging_format", "plaintext", "Logging format: plaintext, json or CEF")
dbHost := flag.String("db_host", "", "Host to db")
Expand Down Expand Up @@ -108,9 +110,18 @@ func main() {
useTLS := flag.Bool("acraconnector_tls_transport_enable", false, "Use tls to encrypt transport between AcraServer and AcraConnector/client")
tlsKey := flag.String("tls_key", "", "Path to private key that will be used in AcraServer's TLS handshake with AcraConnector as server's key and database as client's key")
tlsCert := flag.String("tls_cert", "", "Path to tls certificate")
tlsCA := flag.String("tls_ca", "", "Path to root certificate which will be used with system root certificates to validate Postgresql's and AcraConnector's certificate")
tlsDbSNI := flag.String("tls_db_sni", "", "Expected Server Name (SNI) from database")
tlsAuthType := flag.Int("tls_auth", int(tls.RequireAndVerifyClientCert), "Set authentication mode that will be used in TLS connection with AcraConnector. Values in range 0-4 that set auth type (https://golang.org/pkg/crypto/tls/#ClientAuthType). Default is tls.RequireAndVerifyClientCert")
tlsCA := flag.String("tls_ca", "", "Path to additional CA certificate for AcraConnector and database certificate validation")
tlsAuthType := flag.Int("tls_auth", int(tls.RequireAndVerifyClientCert), "Set authentication mode that will be used in TLS connection with AcraConnector and database. Values in range 0-4 that set auth type (https://golang.org/pkg/crypto/tls/#ClientAuthType). Default is tls.RequireAndVerifyClientCert")
tlsClientAuthType := flag.Int("tls_client_auth", tlsAuthNotSet, "Set authentication mode that will be used in TLS connection with AcraConnector. Overrides the \"tls_auth\" setting.")
tlsClientCA := flag.String("tls_client_ca", "", "Path to additional CA certificate for AcraConnector certificate validation (setup if AcraConnector certificate CA is different from database certificate CA)")
tlsClientCert := flag.String("tls_client_cert", "", "Path to server TLS certificate presented to AcraConnectors (overrides \"tls_cert\")")
tlsClientKey := flag.String("tls_client_key", "", "Path to private key of the TLS certificate presented to AcraConnectors (see \"tls_client_cert\")")
tlsDbAuthType := flag.Int("tls_database_auth", tlsAuthNotSet, "Set authentication mode that will be used in TLS connection with database. Overrides the \"tls_auth\" setting.")
tlsDbCA := flag.String("tls_database_ca", "", "Path to additional CA certificate for database certificate validation (setup if database certificate CA is different from AcraConnector certificate CA)")
tlsDbSNI := flag.String("tls_database_sni", "", "Expected Server Name (SNI) from database")
tlsDbSNIOld := flag.String("tls_db_sni", "", "Expected Server Name (SNI) from database (deprecated, use \"tls_database_sni\" instead)")
tlsDbCert := flag.String("tls_database_cert", "", "Path to client TLS certificate shown to database during TLS handshake (overrides \"tls_cert\")")
tlsDbKey := flag.String("tls_database_key", "", "Path to private key of the TLS certificate used to connect to database (see \"tls_database_cert\")")
noEncryptionTransport := flag.Bool("acraconnector_transport_encryption_disable", false, "Use raw transport (tcp/unix socket) between AcraServer and AcraConnector/client (don't use this flag if you not connect to database with SSL/TLS")
clientID := flag.String("client_id", "", "Expected client ID of AcraConnector in mode without encryption")
acraConnectionString := flag.String("incoming_connection_string", network.BuildConnectionString(cmd.DefaultAcraServerConnectionProtocol, cmd.DefaultAcraServerHost, cmd.DefaultAcraServerPort, ""), "Connection string like tcp://x.x.x.x:yyyy or unix:///path/to/socket")
Expand Down Expand Up @@ -215,19 +226,55 @@ func main() {
log.Infof("Keystore init OK")

log.Infof("Configuring transport...")
var tlsConfig *tls.Config
var clientTLSConfig, dbTLSConfig *tls.Config
if *useTLS || *tlsKey != "" {
tlsConfig, err = network.NewTLSConfig(network.SNIOrHostname(*tlsDbSNI, *dbHost), *tlsCA, *tlsKey, *tlsCert, tls.ClientAuthType(*tlsAuthType))
// Use common TLS settings, unless the user requests specific ones
if *tlsClientCA == "" {
*tlsClientCA = *tlsCA
}
if *tlsClientAuthType == tlsAuthNotSet {
*tlsClientAuthType = *tlsAuthType
}
if *tlsClientCert == "" {
*tlsClientCert = *tlsCert
}
if *tlsClientKey == "" {
*tlsClientKey = *tlsKey
}
clientTLSConfig, err = network.NewTLSConfig("", *tlsClientCA, *tlsClientKey, *tlsClientCert, tls.ClientAuthType(*tlsClientAuthType))
if err != nil {
log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorTransportConfiguration).
Errorln("Configuration error: can't create AcraConnector TLS config")
os.Exit(1)
}
// Use common TLS settings, unless the user requests specific ones.
// Also handle deprecated options.
if *tlsDbCA == "" {
*tlsDbCA = *tlsCA
}
if *tlsDbAuthType == tlsAuthNotSet {
*tlsDbAuthType = *tlsAuthType
}
if *tlsDbSNI == "" && *tlsDbSNIOld != "" {
*tlsDbSNI = *tlsDbSNIOld
}
if *tlsDbCert == "" {
*tlsDbCert = *tlsCert
}
if *tlsDbKey == "" {
*tlsDbKey = *tlsKey
}
dbTLSConfig, err = network.NewTLSConfig(network.SNIOrHostname(*tlsDbSNI, *dbHost), *tlsDbCA, *tlsDbKey, *tlsDbCert, tls.ClientAuthType(*tlsDbAuthType))
if err != nil {
log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorTransportConfiguration).
Errorln("Configuration error: can't get config for TLS")
Errorln("Configuration error: can't create database TLS config")
os.Exit(1)
}
log.Infoln("Loaded tls config")
log.Infoln("Loaded TLS configuration")
}
if *useTLS {
log.Println("Selecting transport: use TLS transport wrapper")
config.ConnectionWrapper, err = network.NewTLSConnectionWrapper([]byte(*clientID), tlsConfig)
config.ConnectionWrapper, err = network.NewTLSConnectionWrapper([]byte(*clientID), clientTLSConfig)
if err != nil {
log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorTransportConfiguration).
Errorln("Configuration error: can't initialise TLS connection wrapper")
Expand Down Expand Up @@ -285,15 +332,15 @@ func main() {
var proxyFactory base.ProxyFactory
if *useMysql {
decryptorFactory = mysql.NewMysqlDecryptorFactory(decryptorSetting)
proxyFactory, err = mysql.NewProxyFactory(base.NewProxySetting(decryptorFactory, config.GetTableSchema(), keyStore, tlsConfig, config.GetCensor()))
proxyFactory, err = mysql.NewProxyFactory(base.NewProxySetting(decryptorFactory, config.GetTableSchema(), keyStore, clientTLSConfig, dbTLSConfig, config.GetCensor()))
if err != nil {
log.WithError(err).Errorln("Can't initialize proxy for connections")
os.Exit(1)
}
sqlparser.SetDefaultDialect(mysqlDialect.NewMySQLDialect())
} else {
decryptorFactory = postgresql.NewDecryptorFactory(decryptorSetting)
proxyFactory, err = postgresql.NewProxyFactory(base.NewProxySetting(decryptorFactory, config.GetTableSchema(), keyStore, tlsConfig, config.GetCensor()))
proxyFactory, err = postgresql.NewProxyFactory(base.NewProxySetting(decryptorFactory, config.GetTableSchema(), keyStore, clientTLSConfig, dbTLSConfig, config.GetCensor()))
if err != nil {
log.WithError(err).Errorln("Can't initialize proxy for connections")
os.Exit(1)
Expand Down
31 changes: 29 additions & 2 deletions configs/acra-server.yaml
Expand Up @@ -113,16 +113,43 @@ postgresql_enable: false
# Id that will be sent in secure session
securesession_id: acra_server

# Set authentication mode that will be used in TLS connection with AcraConnector. Values in range 0-4 that set auth type (https://golang.org/pkg/crypto/tls/#ClientAuthType). Default is tls.RequireAndVerifyClientCert
# Set authentication mode that will be used in TLS connection with AcraConnector and database. Values in range 0-4 that set auth type (https://golang.org/pkg/crypto/tls/#ClientAuthType). Default is tls.RequireAndVerifyClientCert
tls_auth: 4

# Path to root certificate which will be used with system root certificates to validate Postgresql's and AcraConnector's certificate
# Path to additional CA certificate for AcraConnector and database certificate validation
tls_ca:

# Path to tls certificate
tls_cert:

# Set authentication mode that will be used in TLS connection with AcraConnector. Overrides the "tls_auth" setting.
tls_client_auth: -1

# Path to additional CA certificate for AcraConnector certificate validation (setup if AcraConnector certificate CA is different from database certificate CA)
tls_client_ca:

# Path to server TLS certificate presented to AcraConnectors (overrides "tls_cert")
tls_client_cert:

# Path to private key of the TLS certificate presented to AcraConnectors (see "tls_client_cert")
tls_client_key:

# Set authentication mode that will be used in TLS connection with database. Overrides the "tls_auth" setting.
tls_database_auth: -1

# Path to additional CA certificate for database certificate validation (setup if database certificate CA is different from AcraConnector certificate CA)
tls_database_ca:

# Path to client TLS certificate shown to database during TLS handshake (overrides "tls_cert")
tls_database_cert:

# Path to private key of the TLS certificate used to connect to database (see "tls_database_cert")
tls_database_key:

# Expected Server Name (SNI) from database
tls_database_sni:

# Expected Server Name (SNI) from database (deprecated, use "tls_database_sni" instead)
tls_db_sni:

# Path to private key that will be used in AcraServer's TLS handshake with AcraConnector as server's key and database as client's key
Expand Down
21 changes: 14 additions & 7 deletions decryptor/base/proxy.go
Expand Up @@ -30,15 +30,17 @@ import (
type ProxySetting interface {
KeyStore() keystore.DecryptionKeyStore
TableSchemaStore() config.TableSchemaStore
TLSConfig() *tls.Config
ClientTLSConfig() *tls.Config
DatabaseTLSConfig() *tls.Config
Censor() acracensor.AcraCensorInterface
DecryptorFactory() DecryptorFactory
}

type proxySetting struct {
keystore keystore.DecryptionKeyStore
tableSchemaStore config.TableSchemaStore
tlsConfig *tls.Config
clientTLSConfig *tls.Config
dbTLSConfig *tls.Config
censor acracensor.AcraCensorInterface
decryptorFactory DecryptorFactory
}
Expand All @@ -53,9 +55,14 @@ func (p *proxySetting) Censor() acracensor.AcraCensorInterface {
return p.censor
}

// TLSConfig return tls.Config
func (p *proxySetting) TLSConfig() *tls.Config {
return p.tlsConfig
// ClientTLSConfig return tls.Config to use when accepting connections from AcraConnectors.
func (p *proxySetting) ClientTLSConfig() *tls.Config {
return p.clientTLSConfig
}

// DatabaseTLSConfig return tls.Config to use when connecting to the database.
func (p *proxySetting) DatabaseTLSConfig() *tls.Config {
return p.dbTLSConfig
}

// TableSchemaStore return table schema store
Expand All @@ -69,8 +76,8 @@ func (p *proxySetting) KeyStore() keystore.DecryptionKeyStore {
}

// NewProxySetting return new ProxySetting implementation with data from params
func NewProxySetting(decryptorFactory DecryptorFactory, tableSchema config.TableSchemaStore, keystore keystore.DecryptionKeyStore, tlsConfig *tls.Config, censor acracensor.AcraCensorInterface) ProxySetting {
return &proxySetting{keystore: keystore, tableSchemaStore: tableSchema, tlsConfig: tlsConfig, censor: censor, decryptorFactory: decryptorFactory}
func NewProxySetting(decryptorFactory DecryptorFactory, tableSchema config.TableSchemaStore, keystore keystore.DecryptionKeyStore, clientTLSConfig, dbTLSConfig *tls.Config, censor acracensor.AcraCensorInterface) ProxySetting {
return &proxySetting{keystore: keystore, tableSchemaStore: tableSchema, clientTLSConfig: clientTLSConfig, dbTLSConfig: dbTLSConfig, censor: censor, decryptorFactory: decryptorFactory}
}

// Proxy interface to process client's requests to database and responses
Expand Down
2 changes: 1 addition & 1 deletion decryptor/mysql/proxy.go
Expand Up @@ -46,7 +46,7 @@ func (factory *proxyFactory) New(ctx context.Context, clientID []byte, dbConnect
if err != nil {
return nil, err
}
proxy, err := NewMysqlProxy(ctx, decryptor, dbConnection, clientConnection, factory.setting.TLSConfig(), factory.setting.Censor())
proxy, err := NewMysqlProxy(ctx, decryptor, dbConnection, clientConnection, factory.setting)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions decryptor/mysql/proxy_test.go
Expand Up @@ -26,7 +26,7 @@ func (store *tableSchemaStore) IsEmpty() bool {
func TestEncryptorTurnOnOff(t *testing.T) {
emptyStore := &tableSchemaStore{true}
nonEmptyStore := &tableSchemaStore{false}
setting := base.NewProxySetting(&decryptorFactory{}, emptyStore, nil, nil, nil)
setting := base.NewProxySetting(&decryptorFactory{}, emptyStore, nil, nil, nil, nil)
proxyFactory, err := NewProxyFactory(setting)
if err != nil {
t.Fatal(setting)
Expand All @@ -39,7 +39,7 @@ func TestEncryptorTurnOnOff(t *testing.T) {
t.Fatal("Unexpected observers count")
}

setting = base.NewProxySetting(&decryptorFactory{}, nonEmptyStore, nil, nil, nil)
setting = base.NewProxySetting(&decryptorFactory{}, nonEmptyStore, nil, nil, nil, nil)
proxyFactory, err = NewProxyFactory(setting)
if err != nil {
t.Fatal(setting)
Expand Down
31 changes: 18 additions & 13 deletions decryptor/mysql/response_proxy.go
Expand Up @@ -151,21 +151,16 @@ type Handler struct {
dbTLSHandshakeFinished chan bool
clientConnection net.Conn
dbConnection net.Conn
tlsConfig *tls.Config
clientTLSConfig *tls.Config
dbTLSConfig *tls.Config
logger *logrus.Entry
ctx context.Context
queryObserverManager base.QueryObserverManager
decryptionObserver base.ColumnDecryptionObserver
}

// NewMysqlProxy returns new Handler
func NewMysqlProxy(ctx context.Context, decryptor base.Decryptor, dbConnection, clientConnection net.Conn, tlsConfig *tls.Config, censor acracensor.AcraCensorInterface) (*Handler, error) {
var newTLSConfig *tls.Config
if tlsConfig != nil {
// use less secure protocol versions because some drivers and db images doesn't support secure and modern options
newTLSConfig = tlsConfig.Clone()
network.SetMySQLCompatibleTLSSettings(newTLSConfig)
}
func NewMysqlProxy(ctx context.Context, decryptor base.Decryptor, dbConnection, clientConnection net.Conn, setting base.ProxySetting) (*Handler, error) {
observerManager, err := base.NewArrayQueryObserverableManager(ctx)
if err != nil {
return nil, err
Expand All @@ -176,17 +171,27 @@ func NewMysqlProxy(ctx context.Context, decryptor base.Decryptor, dbConnection,
clientDeprecateEOF: false,
decryptor: decryptor,
responseHandler: defaultResponseHandler,
acracensor: censor,
acracensor: setting.Censor(),
clientConnection: clientConnection,
dbConnection: dbConnection,
tlsConfig: newTLSConfig,
clientTLSConfig: tweakTLSConfigForMySQL(setting.ClientTLSConfig()),
dbTLSConfig: tweakTLSConfigForMySQL(setting.DatabaseTLSConfig()),
ctx: ctx,
logger: logging.GetLoggerFromContext(ctx),
queryObserverManager: observerManager,
decryptionObserver: base.NewColumnDecryptionObserver(),
}, nil
}

func tweakTLSConfigForMySQL(config *tls.Config) *tls.Config {
vixentael marked this conversation as resolved.
Show resolved Hide resolved
if config != nil {
// use less secure protocol versions because some drivers and db images doesn't support secure and modern options
config = config.Clone()
network.SetMySQLCompatibleTLSSettings(config)
}
return config
}

// SubscribeOnColumnDecryption subscribes for OnColumn notifications about the column, indexed from left to right starting with zero.
func (handler *Handler) SubscribeOnColumnDecryption(i int, subscriber base.DecryptionSubscriber) {
handler.decryptionObserver.SubscribeOnColumnDecryption(i, subscriber)
Expand Down Expand Up @@ -273,7 +278,7 @@ func (handler *Handler) ProxyClientConnection(errCh chan<- error) {
handler.clientDeprecateEOF = packet.IsClientDeprecateEOF()
clientLog = clientLog.WithField("deprecate_eof", handler.clientDeprecateEOF)
if packet.IsSSLRequest() {
if handler.tlsConfig == nil {
if handler.clientTLSConfig == nil {
handler.logger.WithField(logging.FieldKeyEventCode, logging.EventCodeErrorDecryptorCantInitializeTLS).Errorln("To support TLS connections you must pass TLS key and certificate for AcraServer that will be used " +
"for connections AcraServer->Database and CA certificate which will be used to verify certificate " +
"from database")
Expand All @@ -287,7 +292,7 @@ func (handler *Handler) ProxyClientConnection(errCh chan<- error) {
errCh <- network.ErrEmptyTLSConfig
return
}
tlsConnection := tls.Server(handler.clientConnection, handler.tlsConfig)
tlsConnection := tls.Server(handler.clientConnection, handler.clientTLSConfig)
if err := tlsConnection.Handshake(); err != nil {
handler.logger.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorDecryptorCantInitializeTLS).
Errorln("Error in tls handshake with client")
Expand Down Expand Up @@ -871,7 +876,7 @@ func (handler *Handler) ProxyDatabaseConnection(errCh chan<- error) {
if netErr.Timeout() && handler.isTLSHandshake {
// reset deadline
handler.dbConnection.SetReadDeadline(time.Time{})
tlsConnection := tls.Client(handler.dbConnection, handler.tlsConfig)
tlsConnection := tls.Client(handler.dbConnection, handler.dbTLSConfig)
if err = tlsConnection.Handshake(); err != nil {
handler.logger.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorDecryptorCantInitializeTLS).
Errorln("Error in tls handshake with db")
Expand Down