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

mysql tls support #140

Merged
merged 11 commits into from
Apr 2, 2018
3 changes: 2 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ jobs:
POSTGRES_USER: test
POSTGRES_DB: test
# use the same credentials for mysql db as for postgresql (which support was added first)
- image: mysql:latest
# has latest tag on 2018.03.29
- image: mysql:5.7.21
environment:
MYSQL_DATABASE: test
MYSQL_USER: test
Expand Down
9 changes: 8 additions & 1 deletion .circleci/integration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,16 @@ for version in $VERSIONS; do
#export TEST_DB_NAME=${MYSQL_DATABASE}
export TEST_DB_PORT=3306
export TEST_MYSQL=true
export TEST_TLS=off


echo "-------------------- Testing TEST_MYSQL with TEST_TLS=off"
export TEST_TLS=off
python3 tests/test.py -v;
if [ "$?" != "0" ]; then echo "mysql-$version" >> "$FILEPATH_ERROR_FLAG";
fi

echo "-------------------- Testing TEST_MYSQL with TEST_TLS=on"
export TEST_TLS=on
python3 tests/test.py -v;
if [ "$?" != "0" ]; then echo "mysql-$version" >> "$FILEPATH_ERROR_FLAG";
fi
Expand Down
3 changes: 1 addition & 2 deletions cmd/acraproxy/acraproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,6 @@ func main() {
go sigHandler.Register()
sigHandler.AddListener(listener)


if *useTls {
log.Infof("Selecting transport: use TLS transport wrapper")
tlsConfig, err := network.NewTLSConfig(*tlsSNI, *tlsCA, *tlsKey, *tlsCert)
Expand Down Expand Up @@ -298,7 +297,7 @@ func main() {
commandsListener, err := network.Listen(*connectionAPIString)
if err != nil {
log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorCantStartListenConnections).
Errorln("System error: can't start listen connections to http API")
Errorln("System error: can't start listen connections to http API")
os.Exit(1)
}
sigHandler.AddListener(commandsListener)
Expand Down
44 changes: 31 additions & 13 deletions cmd/acraserver/acraserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package main

import (
"crypto/tls"
"errors"
"flag"
"net/http"
Expand All @@ -33,14 +34,19 @@ import (

var restartSignalsChannel chan os.Signal
var errorSignalChannel chan os.Signal
var err error // unused?

const (
ACRASERVER_WAIT_TIMEOUT = 10
GRACEFUL_ENV = "GRACEFUL_RESTART"
DESCRIPTOR_ACRA = 3
DESCRIPTOR_API = 4
SERVICE_NAME = "acraserver"
TEST_MODE = "true"
)

var Test = "false"

const (
DEFAULT_ACRASERVER_WAIT_TIMEOUT = 10
GRACEFUL_ENV = "GRACEFUL_RESTART"
DESCRIPTOR_ACRA = 3
DESCRIPTOR_API = 4
SERVICE_NAME = "acraserver"
)

// DEFAULT_CONFIG_PATH relative path to config which will be parsed as default
Expand Down Expand Up @@ -73,6 +79,7 @@ func main() {

debug := flag.Bool("d", false, "Turn on debug logging")
debugServer := flag.Bool("ds", false, "Turn on http debug server")
closeConnectionTimeout := flag.Int("close_connections_timeout", DEFAULT_ACRASERVER_WAIT_TIMEOUT, "Time that acraserver will wait (in seconds) on restart before closing all connections")

stopOnPoison := flag.Bool("poisonshutdown", false, "Stop on detecting poison record")
scriptOnPoison := flag.String("poisonscript", "", "Execute script on detecting poison record")
Expand Down Expand Up @@ -128,11 +135,13 @@ func main() {
}

if err := config.SetMySQL(*useMysql); err != nil {
log.WithError(err).Errorln("can't set MySQL support")
log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorWrongConfiguration).
Errorln("can't set MySQL support")
os.Exit(1)
}
if err := config.SetPostgresql(*usePostgresql); err != nil {
log.WithError(err).Errorln("can't set PostgreSQL support")
log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorWrongConfiguration).
Errorln("can't set PostgreSQL support")
os.Exit(1)
}

Expand Down Expand Up @@ -169,14 +178,23 @@ func main() {
Errorln("Can't initialise keystore")
os.Exit(1)
}
if *useTls {
log.Infof("Selecting transport: use TLS transport wrapper")
tlsConfig, err := network.NewTLSConfig(*tlsSNI, *tlsCA, *tlsKey, *tlsCert)
var tlsConfig *tls.Config
if *useTls || *tlsKey != "" {
tlsConfig, err = network.NewTLSConfig(*tlsSNI, *tlsCA, *tlsKey, *tlsCert)
if err != nil {
log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorTransportConfiguration).
Errorln("Configuration error: can't get config for TLS")
os.Exit(1)
}
// need for testing with mysql docker container that always generate new certificates
if Test == TEST_MODE {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't it be better to rename Test into SkipVerifyingCertificated?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought to name it more common to use in future tests if need

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about 'IsRunningTest'? 'TestOnly'?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

by the way, why do you check
if Test == TEST_MODE

instead of if Test == True?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on compile time we can change only string values

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will rename to TestOnly

tlsConfig.InsecureSkipVerify = true
log.Warningln("only for tests!")
}
}
config.SetTLSConfig(tlsConfig)
if *useTls {
log.Println("use TLS transport wrapper")
config.ConnectionWrapper, err = network.NewTLSConnectionWrapper([]byte(*clientId), tlsConfig)
if err != nil {
log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorTransportConfiguration).
Expand Down Expand Up @@ -252,7 +270,7 @@ func main() {
// Stop accepting new connections
server.StopListeners()
// Wait a maximum of N seconds for existing connections to finish
err := server.WaitWithTimeout(ACRASERVER_WAIT_TIMEOUT * time.Second)
err := server.WaitWithTimeout(time.Duration(*closeConnectionTimeout) * time.Second)
if err == ErrWaitTimeout {
log.Warningf("Server shutdown Timeout: %d active connections will be cut", server.ConnectionsCounter())
server.Close()
Expand Down Expand Up @@ -303,7 +321,7 @@ func main() {
log.Infof("%s process forked to PID: %v", SERVICE_NAME, fork)

// Wait a maximum of N seconds for existing connections to finish
err = server.WaitWithTimeout(ACRASERVER_WAIT_TIMEOUT * time.Second)
err = server.WaitWithTimeout(time.Duration(*closeConnectionTimeout) * time.Second)
if err == ErrWaitTimeout {
log.Warningf("Server shutdown Timeout: %d active connections will be cut", server.ConnectionsCounter())
os.Exit(0)
Expand Down
19 changes: 13 additions & 6 deletions cmd/acraserver/client_commands_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package main

import (
"bufio"
"github.com/cossacklabs/acra/logging"
log "github.com/sirupsen/logrus"
"net"
"net/http"
Expand Down Expand Up @@ -52,7 +53,8 @@ func (clientSession *ClientCommandsSession) close() {
log.Debugln("Close acraproxy connection")
err := clientSession.connection.Close()
if err != nil {
log.WithError(err).Errorln("Error during closing connection to acraproxy")
log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorCantCloseConnection).
Errorln("Error during closing connection to acraproxy")
}
log.Debugln("All connections closed")
}
Expand All @@ -61,7 +63,9 @@ func (clientSession *ClientCommandsSession) HandleSession() {
reader := bufio.NewReader(clientSession.connection)
req, err := http.ReadRequest(reader)
if err != nil {
log.WithError(err).Warningln("Got new command request, but can't read it")

log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorGeneral).
Warningln("Got new command request, but can't read it")
clientSession.close()
return
}
Expand Down Expand Up @@ -89,7 +93,8 @@ func (clientSession *ClientCommandsSession) HandleSession() {
log.Debugln("Got /getConfig request")
jsonOutput, err := clientSession.config.ToJson()
if err != nil {
log.WithError(err).Warningln("Can't convert config to JSON")
log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorGeneral).
Warningln("Can't convert config to JSON")
response = "HTTP/1.1 500 Server error\r\n\r\n\r\n\r\n"
} else {
log.Debugln("Handled request correctly")
Expand All @@ -102,7 +107,8 @@ func (clientSession *ClientCommandsSession) HandleSession() {
var configFromUI UIEditableConfig
err := decoder.Decode(&configFromUI)
if err != nil {
log.WithError(err).Warningln("Can't convert config from incoming")
log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorGeneral).
Warningln("Can't convert config from incoming")
response = "HTTP/1.1 500 Server error\r\n\r\n\r\n\r\n"
return
}
Expand All @@ -117,7 +123,8 @@ func (clientSession *ClientCommandsSession) HandleSession() {

err = cmd.DumpConfig(clientSession.Server.config.GetConfigPath(), false)
if err != nil {
log.WithError(err).Errorln("DumpConfig failed")
log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorCantDumpConfig).
Errorln("DumpConfig failed")
response = "HTTP/1.1 500 Server error\r\n\r\n\r\n\r\n"
return

Expand All @@ -128,7 +135,7 @@ func (clientSession *ClientCommandsSession) HandleSession() {

_, err = clientSession.connection.Write([]byte(response))
if err != nil {
log.WithError(err).Errorln("Can't send data with secure session to acraproxy")
log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorGeneral).Errorln("Can't send data with secure session to acraproxy")
return
}
clientSession.close()
Expand Down
21 changes: 4 additions & 17 deletions cmd/acraserver/client_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,33 +89,20 @@ func (clientSession *ClientSession) HandleSecureSession(decryptorImpl base.Decry
return
}

log.Debugf("Initializing config to postgresql decryptor")

pgDecryptorConfig, err := postgresql.NewPgDecryptorConfig(clientSession.config.GetTLSServerKeyPath(), clientSession.config.GetTLSServerCertPath())
if err != nil {
log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorCantInitDecryptor).
Errorln("Can't initialize config for postgresql decryptor, closing connection")
err = clientSession.connection.Close()
if err != nil {
log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorCantCloseConnectionToService).
Errorln("Error with closing connection to acraproxy")
}
return
}
if clientSession.config.UseMySQL() {
log.Debugln("MySQL connection")
handler, err := mysql.NewMysqlHandler(decryptorImpl, clientSession.config.firewall)
handler, err := mysql.NewMysqlHandler(decryptorImpl, clientSession.connectionToDb, clientSession.connection, clientSession.config.GetTLSConfig(), clientSession.config.firewall)
if err != nil {
log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorCantInitDecryptor).
Errorln("Can't initialize mysql handler")
return
}
go handler.ClientToDbProxy(decryptorImpl, clientSession.connectionToDb, clientSession.connection, innerErrorChannel)
go handler.DbToClientProxy(decryptorImpl, clientSession.connectionToDb, clientSession.connection, innerErrorChannel)
go handler.ClientToDbProxy(innerErrorChannel)
go handler.DbToClientProxy(innerErrorChannel)
} else {
log.Debugln("PostgreSQL connection")
go network.Proxy(clientSession.connection, clientSession.connectionToDb, innerErrorChannel)
go postgresql.PgDecryptStream(decryptorImpl, pgDecryptorConfig, clientSession.connectionToDb, clientSession.connection, innerErrorChannel)
go postgresql.PgDecryptStream(decryptorImpl, clientSession.config.GetTLSConfig(), clientSession.connectionToDb, clientSession.connection, innerErrorChannel)
}
for {
err = <-innerErrorChannel
Expand Down
9 changes: 9 additions & 0 deletions cmd/acraserver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package main

import (
"crypto/tls"
"encoding/json"
"errors"

Expand Down Expand Up @@ -50,6 +51,7 @@ type Config struct {
configPath string
debug bool
firewall firewall.FirewallInterface
tlsConfig *tls.Config
}

type UIEditableConfig struct {
Expand Down Expand Up @@ -241,3 +243,10 @@ func (config *Config) GetAcraConnectionString() string {
func (config *Config) GetAcraAPIConnectionString() string {
return config.acraAPIConnectionString
}

func (config *Config) SetTLSConfig(tlsConfig *tls.Config) {
config.tlsConfig = tlsConfig
}
func (config *Config) GetTLSConfig() *tls.Config {
return config.tlsConfig
}
2 changes: 1 addition & 1 deletion cmd/acraserver/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ import (
"github.com/cossacklabs/acra/decryptor/mysql"
pg "github.com/cossacklabs/acra/decryptor/postgresql"
"github.com/cossacklabs/acra/keystore"
"github.com/cossacklabs/acra/logging"
"github.com/cossacklabs/acra/network"
"github.com/cossacklabs/acra/zone"
"github.com/cossacklabs/acra/logging"
log "github.com/sirupsen/logrus"
)

Expand Down
6 changes: 3 additions & 3 deletions decryptor/mysql/decryptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import (
"github.com/cossacklabs/acra/decryptor/binary"
"github.com/cossacklabs/acra/decryptor/postgresql"
"github.com/cossacklabs/acra/keystore"
"github.com/cossacklabs/acra/logging"
"github.com/cossacklabs/acra/utils"
"github.com/cossacklabs/acra/zone"
"github.com/cossacklabs/themis/gothemis/keys"
log "github.com/sirupsen/logrus"
"github.com/cossacklabs/acra/logging"
)

type decryptFunc func([]byte) ([]byte, error)
Expand Down Expand Up @@ -172,12 +172,12 @@ func (decryptor *MySQLDecryptor) decryptBlock(reader io.Reader, id []byte, keyFu
}
key, _, err := decryptor.ReadSymmetricKey(privateKey, reader)
if err != nil {
decryptor.log.WithError(err).Warningln("Can't unwrap symmetric key")
decryptor.log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorDecryptorCantDecryptSymmetricKey).Warningln("Can't unwrap symmetric key")
return []byte{}, err
}
data, err := decryptor.ReadData(key, id, reader)
if err != nil {
decryptor.log.WithError(err).Warningln("Can't decrypt data with unwrapped symmetric key")
decryptor.log.WithError(err).WithField(logging.FieldKeyEventCode, logging.EventCodeErrorDecryptorCantDecryptBinary).Warningln("Can't decrypt data with unwrapped symmetric key")
return []byte{}, err
}
return data, nil
Expand Down
32 changes: 26 additions & 6 deletions decryptor/mysql/packet.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
const (
// CLIENT_PROTOCOL_41 - https://dev.mysql.com/doc/internals/en/capability-flags.html#flag-CLIENT_PROTOCOL_41
CLIENT_PROTOCOL_41 = 0x00000200
// SSL_REQUEST - https://dev.mysql.com/doc/internals/en/capability-flags.html#flag-CLIENT_SSL
SSL_REQUEST = 0x00000800
)

const (
Expand Down Expand Up @@ -129,22 +131,40 @@ func (packet *MysqlPacket) IsEOF() bool {
return isOkPacket || isEOFPacket
}

// IsErr return true if packet has ERR_PACKET flag
func (packet *MysqlPacket) IsErr() bool {
return packet.data[0] == ERR_PACKET
}

func (packet *MysqlPacket) getServerCapabilitiesFromGreeting(data []byte) uint16 {
endOfServerVersion := bytes.Index(data[1:], []byte{0}) + 2 // 1 first byte of protocol version and 1 to point to next byte
func (packet *MysqlPacket) getServerCapabilities() int {
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#idm140437490034448
endOfServerVersion := bytes.Index(packet.data[1:], []byte{0}) + 2 // 1 first byte of protocol version and 1 to point to next byte
// 4 bytes connection string + 8 bytes of auth plugin + 1 byte filler
capabilities := data[endOfServerVersion+13 : endOfServerVersion+13+2]
return binary.LittleEndian.Uint16(capabilities)
rawCapabilities := packet.data[endOfServerVersion+13 : endOfServerVersion+13+2]
return int(binary.LittleEndian.Uint16(rawCapabilities))
}

func (packet *MysqlPacket) SupportProtocol41() bool {
capabilities := int(packet.getServerCapabilitiesFromGreeting(packet.data))
func (packet *MysqlPacket) ServerSupportProtocol41() bool {
capabilities := packet.getServerCapabilities()
return (capabilities & CLIENT_PROTOCOL_41) > 0
}

func (packet *MysqlPacket) getClientCapabilities() int {
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#idm140437489940880
return int(binary.LittleEndian.Uint16(packet.data[:2]))
}

func (packet *MysqlPacket) ClientSupportProtocol41() bool {
capabilities := packet.getClientCapabilities()
return (capabilities & CLIENT_PROTOCOL_41) > 0
}

// IsSSLRequest return true if SSL_REQUEST flag up
func (packet *MysqlPacket) IsSSLRequest() bool {
capabilities := packet.getClientCapabilities()
return (capabilities & SSL_REQUEST) > 0
}

// ReadPacket from connection and return MysqlPacket struct with data or error
func ReadPacket(connection net.Conn) (*MysqlPacket, error) {
packet := NewMysqlPacket()
Expand Down
Loading