Merge branch 'MXS-129' into develop

This commit is contained in:
Markus Makela 2015-06-15 16:23:34 +03:00
commit cd865f2552
24 changed files with 2301 additions and 109 deletions

View File

@ -326,6 +326,63 @@ Example:
connection_timeout=300
```
### Service and SSL
This section describes configuration parameters for services that control the SSL/TLS encrption method and the various certificate files involved in it. To enable SSL, you must configure the `ssl` parameter with either `enabled` or `required` and provide the three files for `ssl_cert`, `ssl_key` and `ssl_ca_cert`. After this, MySQL connections to this service can be encrypted with SSL.
#### `ssl`
This enables SSL connections to the service. If this parameter is set to either `required` or `enabled` and the three certificate files can be found (these are explained afterwards), then client connections will be encrypted with SSL. If the parameter is `enabled` then both SSL and non-SSL connections can connect to this service. If the parameter is set to `required` then only SSL connections can be used for this service and non-SSL connections will get an error when they try to connect to the service.
#### `ssl_key`
The SSL private key the service should use. This will be the private key that is used as the server side private key during a client-server SSL handshake. This is a required parameter for SSL enabled services.
#### `ssl_cert`
The SSL certificate the service should use. This will be the public certificate that is used as the server side certificate during a client-server SSL handshake. This is a required parameter for SSL enabled services.
#### `ssl_ca_cert`
This is the Certificate Authority file. It will be used to verify that both the client and the server certificates are valid. This is a required parameter for SSL enabled services.
### `ssl_version`
This parameter controls the level of encryption used. Accepted values are:
* SSLv2
* SSLv3
* TLSv10
* TLSv11
* TLSv12
* MAX
### `ssl_cert_verification_depth`
The maximum length of the certificate authority chain that will be accepted. Accepted values are positive integers.
```
# Example
ssl_cert_verification_depth=10
```
Example SSL enabled service configuration:
```
[ReadWriteSplitService]
type=service
router=readwritesplit
servers=server1,server2,server3
user=myuser
passwd=mypasswd
ssl=required
ssl_cert=/home/markus/certs/server-cert.pem
ssl_key=/home/markus/certs/server-key.pem
ssl_ca_cert=/home/markus/certs/ca.pem
ssl_version=TLSv12
```
This configuration requires all connections to be encryped with SSL. It also specifies that TLSv1.2 should be used as the encryption method. The paths to the server certificate files and the Certificate Authority file are also provided.
### Server
Server sections are used to define the backend database servers that can be formed into a service. A server may be a member of one or more services within MaxScale. Servers are identified by a server name which is the section name in the configuration file. Servers have a type parameter of server, plus address port and protocol parameters.

View File

@ -0,0 +1,15 @@
# MaxScale and SSL
MaxScale supports client side SSL connections. Enabling is done on a per service basis and each service has its own set of certificates.
## SSL Options
Here are the options which relate to SSL and certificates.
Parameter|Values |Description
---------|-----------|--------
ssl | disabled, enabled, required |`disable` disables SSL, `enabled` enables SSL for client connections but still allows non-SSL connections and `required` requires SSL from all client connections. With the `required` option, client connections that do not use SSL will be rejected.
ssl_cert | path to file |Path to server certificate
ssl_key | path to file |Path to server private key
ssl_ca_cert | path to file |Path to Certificate Authority file
ssl_version|SSLV2,SSLV3,TLSV10,TLSV11,TLSV12,MAX| The SSL method level, defaults to highest available encryption level which is TLSv1.2
ssl_cert_verify_depth|integer|Certificate authority certificate verification depth, default is 100.

View File

@ -345,6 +345,8 @@ hashtable_memory_fns(monitorhash,strdup,NULL,free,NULL);
char *weightby;
char *version_string;
char *subservices;
char *ssl,*ssl_cert,*ssl_key,*ssl_ca_cert,*ssl_version;
char* ssl_cert_verify_depth;
bool is_rwsplit = false;
bool is_schemarouter = false;
char *allow_localhost_match_wildcard_host;
@ -353,6 +355,12 @@ hashtable_memory_fns(monitorhash,strdup,NULL,free,NULL);
user = config_get_value(obj->parameters, "user");
auth = config_get_value(obj->parameters, "passwd");
subservices = config_get_value(obj->parameters, "subservices");
ssl = config_get_value(obj->parameters, "ssl");
ssl_cert = config_get_value(obj->parameters, "ssl_cert");
ssl_key = config_get_value(obj->parameters, "ssl_key");
ssl_ca_cert = config_get_value(obj->parameters, "ssl_ca_cert");
ssl_version = config_get_value(obj->parameters, "ssl_version");
ssl_cert_verify_depth = config_get_value(obj->parameters, "ssl_cert_verify_depth");
enable_root_user = config_get_value(
obj->parameters,
"enable_root_user");
@ -420,7 +428,7 @@ hashtable_memory_fns(monitorhash,strdup,NULL,free,NULL);
/** Add the 5.5.5- string to the start of the version string if
* the version string starts with "10.".
* This mimics MariaDB 10.0 replication which adds 5.5.5- for backwards compatibility. */
* This mimics MariaDB 10.0 behavior which adds 5.5.5- for backwards compatibility. */
if(strncmp(version_string,"10.",3) == 0)
{
((SERVICE *)(obj->element))->version_string = malloc((strlen(version_string) +
@ -443,7 +451,84 @@ hashtable_memory_fns(monitorhash,strdup,NULL,free,NULL);
max_slave_rlag_str =
config_get_value(obj->parameters,
"max_slave_replication_lag");
if(ssl)
{
if(ssl_cert == NULL)
{
error_count++;
skygw_log_write(LE,"Error: Server certificate missing for service '%s'."
"Please provide the path to the server certificate by adding the ssl_cert=<path> parameter",
obj->object);
}
if(ssl_ca_cert == NULL)
{
error_count++;
skygw_log_write(LE,"Error: CA Certificate missing for service '%s'."
"Please provide the path to the certificate authority certificate by adding the ssl_ca_cert=<path> parameter",
obj->object);
}
if(ssl_key == NULL)
{
error_count++;
skygw_log_write(LE,"Error: Server private key missing for service '%s'. "
"Please provide the path to the server certificate key by adding the ssl_key=<path> parameter"
,obj->object);
}
if(access(ssl_ca_cert,F_OK) != 0)
{
skygw_log_write(LE,"Error: Certificate authority file for service '%s' not found: %s",
obj->object,
ssl_ca_cert);
error_count++;
}
if(access(ssl_cert,F_OK) != 0)
{
skygw_log_write(LE,"Error: Server certificate file for service '%s' not found: %s",
obj->object,
ssl_cert);
error_count++;
}
if(access(ssl_key,F_OK) != 0)
{
skygw_log_write(LE,"Error: Server private key file for service '%s' not found: %s",
obj->object,
ssl_key);
error_count++;
}
if(error_count == 0)
{
if(serviceSetSSL(obj->element,ssl) != 0)
{
skygw_log_write(LE,"Error: Unknown parameter for service '%s': %s",obj->object,ssl);
error_count++;
}
else
{
serviceSetCertificates(obj->element,ssl_cert,ssl_key,ssl_ca_cert);
if(ssl_version)
{
if(serviceSetSSLVersion(obj->element,ssl_version) != 0)
{
skygw_log_write(LE,"Error: Unknown parameter value for 'ssl_version' for service '%s': %s",obj->object,ssl_version);
error_count++;
}
}
if(ssl_cert_verify_depth)
{
if(serviceSetSSLVerifyDepth(obj->element,atoi(ssl_cert_verify_depth)) != 0)
{
skygw_log_write(LE,"Error: Invalid parameter value for 'ssl_cert_verify_depth' for service '%s': %s",obj->object,ssl_cert_verify_depth);
error_count++;
}
}
}
}
}
if (enable_root_user)
serviceEnableRootUser(
obj->element,
@ -1339,7 +1424,7 @@ int i;
}
else if (strcmp(name, "ms_timestamp") == 0)
{
skygw_set_highp(config_truth_value(value));
skygw_set_highp(config_truth_value((char*)value));
}
else
{
@ -1347,7 +1432,7 @@ int i;
{
if (strcasecmp(name, lognames[i].logname) == 0)
{
if (config_truth_value(value))
if (config_truth_value((char*)value))
skygw_log_enable(lognames[i].logfile);
else
skygw_log_disable(lognames[i].logfile);
@ -1925,6 +2010,12 @@ static char *service_params[] =
"version_string",
"filters",
"weightby",
"ssl_cert",
"ssl_ca_cert",
"ssl",
"ssl_key",
"ssl_version",
"ssl_cert_verify_depth",
NULL
};

File diff suppressed because it is too large Load Diff

View File

@ -40,7 +40,16 @@
* @endverbatim
*/
#define _XOPEN_SOURCE 700
#define OPENSSL_THREAD_DEFINES
#include <my_config.h>
#include <openssl/opensslconf.h>
#if defined(OPENSSL_THREADS)
#define HAVE_OPENSSL_THREADS 1
#else
#define HAVE_OPENSSL_THREADS 0
#endif
#include <ftw.h>
#include <string.h>
#include <strings.h>
@ -197,6 +206,72 @@ static bool resolve_maxscale_conf_fname(
static char* check_dir_access(char* dirname,bool,bool);
static int set_user();
/** SSL multi-threading functions and structures */
/**
* OpenSSL requires this struct to be defined in order to use dynamic locks
*/
struct CRYPTO_dynlock_value
{
SPINLOCK lock;
};
/**
* Create a dynamic OpenSSL lock. The dynamic lock is just a wrapper structure
* around a SPINLOCK structure.
* @param file File name
* @param line Line number
* @return Pointer to new lock or NULL of an error occurred
*/
static struct CRYPTO_dynlock_value *ssl_create_dynlock(const char* file, int line)
{
struct CRYPTO_dynlock_value* lock = malloc(sizeof(struct CRYPTO_dynlock_value));
if(lock)
{
spinlock_init(&lock->lock);
}
return lock;
}
/**
* Lock a dynamic lock for OpenSSL.
* @param mode
* @param n pointer to lock
* @param file File name
* @param line Line number
*/
static void ssl_lock_dynlock(int mode,struct CRYPTO_dynlock_value * n,const char* file, int line)
{
if(mode & CRYPTO_LOCK)
{
spinlock_acquire(&n->lock);
}
else
{
spinlock_release(&n->lock);
}
}
/**
* Free a dynamic OpenSSL lock.
* @param n Lock to free
* @param file File name
* @param line Line number
*/
static void ssl_free_dynlock(struct CRYPTO_dynlock_value * n,const char* file, int line)
{
free(n);
}
/**
* The thread ID callback function for OpenSSL dynamic locks.
* @param id Id to modify
*/
static void maxscale_ssl_id(CRYPTO_THREADID* id)
{
CRYPTO_THREADID_set_numeric(id,pthread_self());
}
/**
* Handler for SIGHUP signal. Reload the configuration for the
* gateway.
@ -1378,6 +1453,22 @@ int main(int argc, char **argv)
rc = MAXSCALE_INTERNALERROR;
goto return_main;
}
/** OpenSSL initialization */
if(!HAVE_OPENSSL_THREADS)
{
char* logerr = "OpenSSL library does not support multi-threading";
print_log_n_stderr(true, true, logerr, logerr, eno);
rc = MAXSCALE_INTERNALERROR;
goto return_main;
}
SSL_library_init();
SSL_load_error_strings();
OPENSSL_add_all_algorithms_noconf();
CRYPTO_set_dynlock_create_callback(ssl_create_dynlock);
CRYPTO_set_dynlock_destroy_callback(ssl_free_dynlock);
CRYPTO_set_dynlock_lock_callback(ssl_lock_dynlock);
CRYPTO_THREADID_set_callback(maxscale_ssl_id);
/* register exit function for embedded MySQL library */
l = atexit(libmysqld_done);
@ -2010,3 +2101,4 @@ static int set_user(char* user)
return rval;
}

View File

@ -69,6 +69,9 @@ extern int lm_enabled_logfiles_bitmask;
extern size_t log_ses_count[];
extern __thread log_info_t tls_log_info;
static RSA *rsa_512 = NULL;
static RSA *rsa_1024 = NULL;
/** To be used with configuration type checks */
typedef struct typelib_st {
int tl_nelems;
@ -136,7 +139,14 @@ SERVICE *service;
service->routerModule = strdup(router);
service->users_from_all = false;
service->resources = NULL;
service->ssl_mode = SSL_DISABLED;
service->ssl_init_done = false;
service->ssl_ca_cert = NULL;
service->ssl_cert = NULL;
service->ssl_key = NULL;
service->ssl_cert_verify_depth = DEFAULT_SSL_CERT_VERIFY_DEPTH;
/** Support the highest possible SSL/TLS methods available as the default */
service->ssl_method_type = SERVICE_SSL_TLS_MAX;
if (service->name == NULL || service->routerModule == NULL)
{
if (service->name)
@ -412,6 +422,17 @@ serviceStart(SERVICE *service)
SERV_PROTOCOL *port;
int listeners = 0;
if(service->ssl_mode != SSL_DISABLED)
{
if(serviceInitSSL(service) != 0)
{
LOGIF(LE, (skygw_log_write_flush(LOGFILE_ERROR,
"%s: SSL initialization failed. Service not started.",
service->name)));
service->state = SERVICE_STATE_FAILED;
return 0;
}
}
if ((service->router_instance = service->router->createInstance(service,
service->routerOptions)) == NULL)
{
@ -855,6 +876,99 @@ serviceOptimizeWildcard(SERVICE *service, int action)
return 1;
}
/**
* Set the locations of the server's SSL certificate, server's private key and the CA
* certificate which both the client and the server should trust.
* @param service Service to configure
* @param cert SSL certificate
* @param key SSL private key
* @param ca_cert SSL CA certificate
*/
void
serviceSetCertificates(SERVICE *service, char* cert,char* key, char* ca_cert)
{
if(service->ssl_cert)
free(service->ssl_cert);
service->ssl_cert = strdup(cert);
if(service->ssl_key)
free(service->ssl_key);
service->ssl_key = strdup(key);
if(service->ssl_ca_cert)
free(service->ssl_ca_cert);
service->ssl_ca_cert = strdup(ca_cert);
}
/**
* Set the maximum SSL/TLS version the service will support
* @param service Service to configure
* @param version SSL/TLS version string
* @return 0 on success, -1 on invalid version string
*/
int
serviceSetSSLVersion(SERVICE *service, char* version)
{
if(strcasecmp(version,"SSLV2") == 0)
service->ssl_method_type = SERVICE_SSLV2;
else if(strcasecmp(version,"SSLV3") == 0)
service->ssl_method_type = SERVICE_SSLV3;
else if(strcasecmp(version,"TLSV10") == 0)
service->ssl_method_type = SERVICE_TLS10;
else if(strcasecmp(version,"TLSV11") == 0)
service->ssl_method_type = SERVICE_TLS11;
else if(strcasecmp(version,"TLSV12") == 0)
service->ssl_method_type = SERVICE_TLS12;
else if(strcasecmp(version,"MAX") == 0)
service->ssl_method_type = SERVICE_SSL_TLS_MAX;
else return -1;
return 0;
}
/**
* Set the service's SSL certificate verification depth. Depth of 0 means the peer
* certificate, 1 is the CA and 2 is a higher CA and so on.
* @param service Service to configure
* @param depth Certificate verification depth
* @return 0 on success, -1 on incorrect depth value
*/
int serviceSetSSLVerifyDepth(SERVICE* service, int depth)
{
if(depth < 0)
return -1;
service->ssl_cert_verify_depth = depth;
return 0;
}
/**
* Enable or disable the service SSL capability of a service.
* The SSL mode string passed as a parameter should be one of required, enabled
* or disabled. Required requires all connections to use SSL encryption, enabled
* allows both SSL and non-SSL connections and disabled does not use SSL encryption.
* If the service SSL mode is set to enabled, then the client will decide whether
* SSL encryption is used.
* @param service Service to configure
* @param action Mode string. One of required, enabled or disabled.
* @return 0 on success, -1 on error
*/
int
serviceSetSSL(SERVICE *service, char* action)
{
int rval = 0;
if(strcasecmp(action,"required") == 0)
service->ssl_mode = SSL_REQUIRED;
else if(strcasecmp(action,"enabled") == 0)
service->ssl_mode = SSL_ENABLED;
else if(strcasecmp(action,"disabled") == 0)
service->ssl_mode = SSL_DISABLED;
else
rval = -1;
return rval;
}
/**
* Whether to strip escape characters from the name of the database the client
* is connecting to.
@ -1018,6 +1132,8 @@ int i;
printf("\tUsers data: %p\n", (void *)service->users);
printf("\tTotal connections: %d\n", service->stats.n_sessions);
printf("\tCurrently connected: %d\n", service->stats.n_current);
printf("\tSSL: %s\n", service->ssl_mode == SSL_DISABLED ? "Disabled":
(service->ssl_mode == SSL_ENABLED ? "Enabled":"Required"));
}
/**
@ -1127,6 +1243,8 @@ int i;
service->stats.n_sessions);
dcb_printf(dcb, "\tCurrently connected: %d\n",
service->stats.n_current);
dcb_printf(dcb,"\tSSL: %s\n", service->ssl_mode == SSL_DISABLED ? "Disabled":
(service->ssl_mode == SSL_ENABLED ? "Enabled":"Required"));
}
/**
@ -1775,3 +1893,142 @@ int *data;
return set;
}
/**
* The RSA ket generation callback function for OpenSSL.
* @param s SSL structure
* @param is_export Not used
* @param keylength Length of the key
* @return Pointer to RSA structure
*/
RSA *tmp_rsa_callback(SSL *s, int is_export, int keylength)
{
RSA *rsa_tmp=NULL;
switch (keylength) {
case 512:
if (rsa_512)
rsa_tmp = rsa_512;
else { /* generate on the fly, should not happen in this example */
rsa_tmp = RSA_generate_key(keylength,RSA_F4,NULL,NULL);
rsa_512 = rsa_tmp; /* Remember for later reuse */
}
break;
case 1024:
if (rsa_1024)
rsa_tmp=rsa_1024;
break;
default:
/* Generating a key on the fly is very costly, so use what is there */
if (rsa_1024)
rsa_tmp=rsa_1024;
else
rsa_tmp=rsa_512; /* Use at least a shorter key */
}
return(rsa_tmp);
}
/**
* Initialize the servce's SSL context. This sets up the generated RSA
* encryption keys, chooses the server encryption level and configures the server
* certificate, private key and certificate authority file.
* @param service
* @return
*/
int serviceInitSSL(SERVICE* service)
{
DH* dh;
RSA* rsa;
if(!service->ssl_init_done)
{
switch(service->ssl_method_type)
{
case SERVICE_SSLV2:
service->method = (SSL_METHOD*)SSLv2_server_method();
break;
case SERVICE_SSLV3:
service->method = (SSL_METHOD*)SSLv3_server_method();
break;
case SERVICE_TLS10:
service->method = (SSL_METHOD*)TLSv1_server_method();
break;
case SERVICE_TLS11:
service->method = (SSL_METHOD*)TLSv1_1_server_method();
break;
case SERVICE_TLS12:
service->method = (SSL_METHOD*)TLSv1_2_server_method();
break;
/** Rest of these use the maximum available SSL/TLS methods */
case SERVICE_SSL_MAX:
service->method = (SSL_METHOD*)SSLv23_server_method();
break;
case SERVICE_TLS_MAX:
service->method = (SSL_METHOD*)SSLv23_server_method();
break;
case SERVICE_SSL_TLS_MAX:
service->method = (SSL_METHOD*)SSLv23_server_method();
break;
default:
service->method = (SSL_METHOD*)SSLv23_server_method();
break;
}
service->ctx = SSL_CTX_new(service->method);
/** Enable all OpenSSL bug fixes */
SSL_CTX_set_options(service->ctx,SSL_OP_ALL);
/** Generate the 512-bit and 1024-bit RSA keys */
if(rsa_512 == NULL)
{
rsa_512 = RSA_generate_key(512,RSA_F4,NULL,NULL);
if (rsa_512 == NULL)
skygw_log_write(LE,"Error: 512-bit RSA key generation failed.");
}
if(rsa_1024 == NULL)
{
rsa_1024 = RSA_generate_key(1024,RSA_F4,NULL,NULL);
if (rsa_1024 == NULL)
skygw_log_write(LE,"Error: 1024-bit RSA key generation failed.");
}
if(rsa_512 != NULL && rsa_1024 != NULL)
SSL_CTX_set_tmp_rsa_callback(service->ctx,tmp_rsa_callback);
/** Load the server sertificate */
if (SSL_CTX_use_certificate_file(service->ctx, service->ssl_cert, SSL_FILETYPE_PEM) <= 0) {
skygw_log_write(LE,"Error: Failed to set server SSL certificate.");
return -1;
}
/* Load the private-key corresponding to the server certificate */
if (SSL_CTX_use_PrivateKey_file(service->ctx, service->ssl_key, SSL_FILETYPE_PEM) <= 0) {
skygw_log_write(LE,"Error: Failed to set server SSL key.");
return -1;
}
/* Check if the server certificate and private-key matches */
if (!SSL_CTX_check_private_key(service->ctx)) {
skygw_log_write(LE,"Error: Server SSL certificate and key do not match.");
return -1;
}
/* Load the RSA CA certificate into the SSL_CTX structure */
if (!SSL_CTX_load_verify_locations(service->ctx, service->ssl_ca_cert, NULL)) {
skygw_log_write(LE,"Error: Failed to set Certificate Authority file.");
return -1;
}
/* Set to require peer (client) certificate verification */
SSL_CTX_set_verify(service->ctx,SSL_VERIFY_PEER,NULL);
/* Set the verification depth */
SSL_CTX_set_verify_depth(service->ctx,service->ssl_cert_verify_depth);
service->ssl_init_done = true;
}
return 0;
}

View File

@ -23,6 +23,9 @@
#include <gwbitmask.h>
#include <skygw_utils.h>
#include <netinet/in.h>
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define ERRHANDLE
@ -265,6 +268,7 @@ typedef struct dcb {
unsigned int high_water; /**< High water mark */
unsigned int low_water; /**< Low water mark */
struct server *server; /**< The associated backend server */
SSL* ssl; /*< SSL struct for connection */
#if defined(SS_DEBUG)
int dcb_port; /**< port of target server */
skygw_chk_t dcb_chk_tail;
@ -312,6 +316,7 @@ void dcb_free(DCB *);
DCB *dcb_connect(struct server *, struct session *, const char *);
DCB *dcb_clone(DCB *);
int dcb_read(DCB *, GWBUF **);
int dcb_read_n(DCB*,GWBUF **,int);
int dcb_drain_writeq(DCB *);
void dcb_close(DCB *);
DCB *dcb_process_zombies(int); /* Process Zombies except the one behind the pointer */
@ -337,7 +342,13 @@ bool dcb_set_state(DCB* dcb, dcb_state_t new_state, dcb_state_t* old_state);
void dcb_call_foreach (struct server* server, DCB_REASON reason);
size_t dcb_get_session_id(DCB* dcb);
bool dcb_get_ses_log_info(DCB* dcb, size_t* sesid, int* enabled_logs);
int dcb_create_SSL(DCB* dcb);
int dcb_accept_SSL(DCB* dcb);
int dcb_connect_SSL(DCB* dcb);
int gw_write_SSL(SSL* ssl, const void *buf, size_t nbytes);
int dcb_write_SSL(DCB *dcb,GWBUF *queue);
int dcb_read_SSL(DCB *dcb,GWBUF **head);
int dcb_drain_writeq_SSL(DCB *dcb);
/**

View File

@ -26,7 +26,10 @@
#include <hashtable.h>
#include <resultset.h>
#include <maxconfig.h>
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/dh.h>
/**
* @file service.h
*
@ -105,6 +108,25 @@ typedef struct server_ref_t{
SERVER* server;
}SERVER_REF;
typedef enum {
SSL_DISABLED,
SSL_ENABLED,
SSL_REQUIRED
} ssl_mode_t;
enum{
SERVICE_SSLV2,
SERVICE_SSLV3,
SERVICE_TLS10,
SERVICE_TLS11,
SERVICE_TLS12,
SERVICE_SSL_MAX,
SERVICE_TLS_MAX,
SERVICE_SSL_TLS_MAX
};
#define DEFAULT_SSL_CERT_VERIFY_DEPTH 100 /*< The default certificate verification depth */
/**
* Defines a service within the gateway.
*
@ -149,8 +171,19 @@ typedef struct service {
FILTER_DEF **filters; /**< Ordered list of filters */
int n_filters; /**< Number of filters */
int conn_timeout; /*< Session timeout in seconds */
ssl_mode_t ssl_mode; /*< one of DISABLED, ENABLED or REQUIRED */
char *weightby;
struct service *next; /**< The next service in the linked list */
SSL_CTX *ctx;
SSL_METHOD *method; /*< SSLv2/3 or TLSv1/2 methods
* see: https://www.openssl.org/docs/ssl/SSL_CTX_new.html */
int ssl_cert_verify_depth; /*< SSL certificate verification depth */
int ssl_method_type; /*< Which of the SSLv2/3 or TLS1.0/1.1/1.2 methods to use */
char* ssl_cert; /*< SSL certificate */
char* ssl_key; /*< SSL private key */
char* ssl_ca_cert; /*< SSL CA certificate */
bool ssl_init_done; /*< If SSL has already been initialized for this service */
} SERVICE;
typedef enum count_spec_t {COUNT_NONE=0, COUNT_ATLEAST, COUNT_EXACT, COUNT_ATMOST} count_spec_t;
@ -178,6 +211,11 @@ extern int serviceRestart(SERVICE *);
extern int serviceSetUser(SERVICE *, char *, char *);
extern int serviceGetUser(SERVICE *, char **, char **);
extern void serviceSetFilters(SERVICE *, char *);
extern int serviceSetSSL(SERVICE *service, char* action);
extern int serviceInitSSL(SERVICE* service);
extern int serviceSetSSLVersion(SERVICE *service, char* version);
extern int serviceSetSSLVerifyDepth(SERVICE* service, int depth);
extern void serviceSetCertificates(SERVICE *service, char* cert,char* key, char* ca_cert);
extern int serviceEnableRootUser(SERVICE *, int );
extern int serviceSetTimeout(SERVICE *, int );
extern void serviceWeightBy(SERVICE *, char *);

View File

@ -54,7 +54,9 @@
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <openssl/crypto.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <service.h>
#include <router.h>
#include <poll.h>
@ -89,6 +91,10 @@
#define COM_QUIT_PACKET_SIZE (4+1)
struct dcb;
#define MYSQL_FAILED_AUTH 1
#define MYSQL_FAILED_AUTH_DB 2
#define MYSQL_FAILED_AUTH_SSL 3
typedef enum {
MYSQL_ALLOC,
MYSQL_PENDING_CONNECT,
@ -97,6 +103,11 @@ typedef enum {
MYSQL_AUTH_RECV,
MYSQL_AUTH_FAILED,
MYSQL_HANDSHAKE_FAILED,
MYSQL_AUTH_SSL_REQ, /*< client requested SSL but SSL_accept hasn't beed called */
MYSQL_AUTH_SSL_HANDSHAKE_DONE, /*< SSL handshake has been fully completed */
MYSQL_AUTH_SSL_HANDSHAKE_FAILED, /*< SSL handshake failed for any reason */
MYSQL_AUTH_SSL_HANDSHAKE_ONGOING, /*< SSL_accept has been called but the
* SSL handshake hasn't been completed */
MYSQL_IDLE
} mysql_auth_state_t;
@ -290,6 +301,7 @@ typedef struct {
unsigned long tid; /*< MySQL Thread ID, in
* handshake */
unsigned int charset; /*< MySQL character set at connect time */
bool use_ssl;
#if defined(SS_DEBUG)
skygw_chk_t protocol_chk_tail;
#endif
@ -309,7 +321,7 @@ typedef struct {
#define MYSQL_IS_CHANGE_USER(payload) (MYSQL_GET_COMMAND(payload)==0x11)
#define MYSQL_GET_NATTR(payload) ((int)payload[4])
#endif /** _MYSQL_PROTOCOL_H */
MySQLProtocol* mysql_protocol_init(DCB* dcb, int fd);
void mysql_protocol_done (DCB* dcb);
@ -405,4 +417,4 @@ void init_response_status (
int* npackets,
ssize_t* nbytes);
#endif /** _MYSQL_PROTOCOL_H */

View File

@ -17,6 +17,7 @@ install(TARGETS HTTPD DESTINATION ${MAXSCALE_LIBDIR})
if(BUILD_TESTS)
add_library(testprotocol SHARED testprotocol.c)
install(TARGETS testprotocol DESTINATION ${MAXSCALE_LIBDIR})
add_subdirectory(test)
endif()
add_library(maxscaled SHARED maxscaled.c)

View File

@ -72,7 +72,7 @@ static void backend_set_delayqueue(DCB *dcb, GWBUF *queue);
static int gw_change_user(DCB *backend_dcb, SERVER *server, SESSION *in_session, GWBUF *queue);
static GWBUF* process_response_data (DCB* dcb, GWBUF* readbuf, int nbytes_to_process);
extern char* create_auth_failed_msg( GWBUF* readbuf, char* hostaddr, uint8_t* sha1);
extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db);
extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db,int);
static bool sescmd_response_complete(DCB* dcb);
@ -1433,7 +1433,7 @@ static int gw_change_user(
message = create_auth_fail_str(username,
backend->session->client->remote,
password_set,
"");
"",auth_ret);
if (message == NULL)
{
LOGIF(LE, (skygw_log_write_flush(

View File

@ -37,7 +37,7 @@
* 09/09/2014 Massimiliano Pinto Added: 777 permission for socket path
* 13/10/2014 Massimiliano Pinto Added: dbname authentication check
* 10/11/2014 Massimiliano Pinto Added: client charset added to protocol struct
*
* 29/05/2015 Markus Makela Added SSL support
*/
#include <skygw_utils.h>
#include <log_manager.h>
@ -70,14 +70,18 @@ static int gw_MySQLWrite_client(DCB *dcb, GWBUF *queue);
static int gw_error_client_event(DCB *dcb);
static int gw_client_close(DCB *dcb);
static int gw_client_hangup_event(DCB *dcb);
int gw_read_client_event_SSL(DCB* dcb);
int gw_MySQLWrite_client_SSL(DCB *dcb, GWBUF *queue);
int gw_write_client_event_SSL(DCB *dcb);
int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char* mysql_message);
int MySQLSendHandshake(DCB* dcb);
static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue);
static int gw_mysql_do_authentication(DCB *dcb, GWBUF **queue);
static int route_by_statement(SESSION *, GWBUF **);
extern char* get_username_from_auth(char* ptr, uint8_t* data);
extern int check_db_name_after_auth(DCB *, char *, int);
extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db);
extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db,int);
int do_ssl_accept(MySQLProtocol* protocol);
/*
* The "module object" for the mysqld client protocol module.
@ -243,7 +247,7 @@ MySQLSendHandshake(DCB* dcb)
char server_scramble[GW_MYSQL_SCRAMBLE_SIZE + 1]="";
char *version_string;
int len_version_string=0;
MySQLProtocol *protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
GWBUF *buf;
@ -320,7 +324,16 @@ MySQLSendHandshake(DCB* dcb)
mysql_server_capabilities_one[0] &= ~GW_MYSQL_CAPABILITIES_COMPRESS;
mysql_server_capabilities_one[0] &= ~GW_MYSQL_CAPABILITIES_SSL;
if(dcb->service->ssl_mode != SSL_DISABLED)
{
mysql_server_capabilities_one[1] |= GW_MYSQL_CAPABILITIES_SSL >> 8;
}
else
{
mysql_server_capabilities_one[0] &= ~GW_MYSQL_CAPABILITIES_SSL;
}
memcpy(mysql_handshake_payload, mysql_server_capabilities_one, sizeof(mysql_server_capabilities_one));
mysql_handshake_payload = mysql_handshake_payload + sizeof(mysql_server_capabilities_one);
@ -376,21 +389,24 @@ MySQLSendHandshake(DCB* dcb)
/**
* gw_mysql_do_authentication
*
* Performs the MySQL protocol 4.1 authentication, using data in GWBUF *queue
* Performs the MySQL protocol 4.1 authentication, using data in GWBUF **queue.
*
* (MYSQL_session*)client_data including: user, db, client_sha1 are copied into
* the dcb->data and later to dcb->session->data.
*
* client_capabilitiesa are copied into the dcb->protocol
* the dcb->data and later to dcb->session->data. client_capabilities are copied
* into the dcb->protocol.
*
* If SSL is enabled for the service, the SSL handshake will be done before the
* MySQL authentication.
*
* @param dcb Descriptor Control Block of the client
* @param queue The GWBUF with data from client
* @param queue Pointer to the location of the GWBUF with data from client
* @return 0 If succeed, otherwise non-zero value
*
* @note in case of failure, dcb->data is freed before returning. If succeed,
* dcb->data is freed in session.c:session_free.
*/
static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
static int gw_mysql_do_authentication(DCB *dcb, GWBUF **buf) {
GWBUF* queue = *buf;
MySQLProtocol *protocol = NULL;
/* int compress = -1; */
int connect_with_db = -1;
@ -403,7 +419,7 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
uint8_t *stage1_hash = NULL;
int auth_ret = -1;
MYSQL_session *client_data = NULL;
int ssl = 0;
CHK_DCB(dcb);
protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
@ -438,7 +454,7 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
/* Detect now if there are enough bytes to continue */
if (client_auth_packet_size < (4 + 4 + 4 + 1 + 23))
{
return 1;
return MYSQL_FAILED_AUTH;
}
memcpy(&protocol->client_capabilities, client_auth_packet + 4, 4);
@ -452,11 +468,66 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
&protocol->client_capabilities);
*/
/** Skip this if the SSL handshake is already done.
* If not, start the SSL handshake. */
if(protocol->protocol_auth_state != MYSQL_AUTH_SSL_HANDSHAKE_DONE)
{
ssl = protocol->client_capabilities & GW_MYSQL_CAPABILITIES_SSL;
/** Client didn't requested SSL when SSL mode was required*/
if(!ssl && protocol->owner_dcb->service->ssl_mode == SSL_REQUIRED)
{
LOGIF(LT,(skygw_log_write(LT,"User %s@%s connected to service '%s' without SSL when SSL was required.",
protocol->owner_dcb->user,
protocol->owner_dcb->remote,
protocol->owner_dcb->service->name)));
return MYSQL_FAILED_AUTH_SSL;
}
if(LOG_IS_ENABLED(LT) && ssl)
{
skygw_log_write(LT,"User %s@%s connected to service '%s' with SSL.",
protocol->owner_dcb->user,
protocol->owner_dcb->remote,
protocol->owner_dcb->service->name);
}
/** Do the SSL Handshake */
if(ssl && protocol->owner_dcb->service->ssl_mode != SSL_DISABLED)
{
protocol->protocol_auth_state = MYSQL_AUTH_SSL_REQ;
if(do_ssl_accept(protocol) < 0)
{
return MYSQL_FAILED_AUTH;
}
else
{
return 0;
}
}
else if(dcb->service->ssl_mode == SSL_ENABLED)
{
/** This is a non-SSL connection to a SSL enabled service.
* We have only read enough of the packet to know that the client
* is not requesting SSL and the rest of the auth packet is still
* waiting in the socket. We need to read the data from the socket
* to find out the username of the connecting client. */
int bytes = dcb_read(dcb,&queue);
queue = gwbuf_make_contiguous(queue);
client_auth_packet = GWBUF_DATA(queue);
client_auth_packet_size = gwbuf_length(queue);
*buf = queue;
LOGIF(LD,(skygw_log_write(LD,"%lu Read %d bytes from fd %d",pthread_self(),bytes,dcb->fd)));
}
}
username = get_username_from_auth(username, client_auth_packet);
if (username == NULL)
{
return 1;
return MYSQL_FAILED_AUTH;
}
/* get charset */
@ -558,6 +629,24 @@ gw_MySQLWrite_client(DCB *dcb, GWBUF *queue)
return dcb_write(dcb, queue);
}
/**
* Write function for client DCB: writes data from MaxScale to Client using SSL
* encryption. The SSH handshake must have already been done.
*
* @param dcb The DCB of the client
* @param queue Queue of buffers to write
*/
int
gw_MySQLWrite_client_SSL(DCB *dcb, GWBUF *queue)
{
MySQLProtocol *protocol = NULL;
CHK_DCB(dcb);
protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
CHK_PROTOCOL(protocol);
return dcb_write_SSL(dcb, queue);
}
/**
* Client read event triggered by EPOLLIN
*
@ -581,9 +670,66 @@ int gw_read_client_event(
CHK_DCB(dcb);
protocol = DCB_PROTOCOL(dcb, MySQLProtocol);
CHK_PROTOCOL(protocol);
rc = dcb_read(dcb, &read_buffer);
#ifdef SS_DEBUG
skygw_log_write(LD,"[gw_read_client_event] Protocol state: %s",
gw_mysql_protocol_state2string(protocol->protocol_auth_state));
#endif
/** SSL authentication is still going on, we need to call do_ssl_accept
* until it return 1 for success or -1 for error */
if(protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_ONGOING ||
protocol->protocol_auth_state == MYSQL_AUTH_SSL_REQ)
{
switch(do_ssl_accept(protocol))
{
case 0:
return 0;
break;
case 1:
{
int b = 0;
ioctl(dcb->fd,FIONREAD,&b);
if(b == 0)
{
skygw_log_write(LD,
"[gw_read_client_event] No data in socket after SSL auth");
return 0;
}
break;
}
case -1:
return 1;
break;
default:
return 1;
break;
}
}
if(protocol->use_ssl)
{
/** SSL handshake is done, communication is now encrypted with SSL */
rc = dcb_read_SSL(dcb, &read_buffer);
}
else if(dcb->service->ssl_mode != SSL_DISABLED &&
protocol->protocol_auth_state == MYSQL_AUTH_SENT)
{
/** The service allows both SSL and non-SSL connections.
* read only enough of the auth packet to know if the client is
* requesting SSL. If the client is not requesting SSL the rest of
the auth packet will be read later. */
rc = dcb_read_n(dcb, &read_buffer,(4 + 4 + 4 + 1 + 23));
}
else
{
/** Normal non-SSL connection */
rc = dcb_read(dcb, &read_buffer);
}
if (rc < 0)
{
dcb_close(dcb);
@ -691,8 +837,8 @@ int gw_read_client_event(
dcb->dcb_readqueue = gwbuf_append(dcb->dcb_readqueue, read_buffer);
nbytes_read = gwbuf_length(dcb->dcb_readqueue);
data = (uint8_t *)GWBUF_DATA(dcb->dcb_readqueue);
if (nbytes_read < 3 || nbytes_read < MYSQL_GET_PACKET_LEN(data))
int plen = MYSQL_GET_PACKET_LEN(data);
if (nbytes_read < 3 || nbytes_read < MYSQL_GET_PACKET_LEN(data) + 4)
{
rc = 0;
goto return_rc;
@ -720,7 +866,7 @@ int gw_read_client_event(
}
}
/**
* Now there should be at least one complete mysql packet in read_buffer.
*/
@ -730,8 +876,19 @@ int gw_read_client_event(
{
int auth_val;
auth_val = gw_mysql_do_authentication(dcb, read_buffer);
auth_val = gw_mysql_do_authentication(dcb, &read_buffer);
if(protocol->protocol_auth_state == MYSQL_AUTH_SSL_REQ ||
protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_ONGOING ||
protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_DONE ||
protocol->protocol_auth_state == MYSQL_AUTH_SSL_HANDSHAKE_FAILED)
{
/** SSL was requested and the handshake is either done or
* still ongoing. After the handshake is done, the client
* will send another auth packet. */
break;
}
if (auth_val == 0)
{
SESSION *session;
@ -797,7 +954,7 @@ int gw_read_client_event(
fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user,
dcb->remote,
(char*)((MYSQL_session *)dcb->data)->client_sha1,
(char*)((MYSQL_session *)dcb->data)->db);
(char*)((MYSQL_session *)dcb->data)->db,auth_val);
modutil_send_mysql_err_packet(dcb, 2, 0, 1045, "28000", fail_str);
}
if (fail_str)
@ -825,6 +982,113 @@ int gw_read_client_event(
}
break;
case MYSQL_AUTH_SSL_HANDSHAKE_DONE:
{
int auth_val;
auth_val = gw_mysql_do_authentication(dcb, &read_buffer);
if (auth_val == 0)
{
SESSION *session;
protocol->protocol_auth_state = MYSQL_AUTH_RECV;
/**
* Create session, and a router session for it.
* If successful, there will be backend connection(s)
* after this point.
*/
session = session_alloc(dcb->service, dcb);
if (session != NULL)
{
CHK_SESSION(session);
ss_dassert(session->state != SESSION_STATE_ALLOC);
protocol->protocol_auth_state = MYSQL_IDLE;
/**
* Send an AUTH_OK packet to the client,
* packet sequence is # 2
*/
mysql_send_ok(dcb, 3, 0, NULL);
}
else
{
protocol->protocol_auth_state = MYSQL_AUTH_FAILED;
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [gw_read_client_event] session "
"creation failed. fd %d, "
"state = MYSQL_AUTH_FAILED.",
pthread_self(),
protocol->owner_dcb->fd)));
/** Send ERR 1045 to client */
mysql_send_auth_error(
dcb,
3,
0,
"failed to create new session");
dcb_close(dcb);
}
}
else
{
char* fail_str = NULL;
protocol->protocol_auth_state = MYSQL_AUTH_FAILED;
if (auth_val == 2) {
/** Send error 1049 to client */
int message_len = 25 + MYSQL_DATABASE_MAXLEN;
fail_str = calloc(1, message_len+1);
snprintf(fail_str, message_len, "Unknown database '%s'",
(char*)((MYSQL_session *)dcb->data)->db);
modutil_send_mysql_err_packet(dcb, 3, 0, 1049, "42000", fail_str);
}else if(auth_val == 3){
/** Send error 1045 to client */
fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user,
dcb->remote,
(char*)((MYSQL_session *)dcb->data)->client_sha1,
(char*)((MYSQL_session *)dcb->data)->db,auth_val);
modutil_send_mysql_err_packet(dcb, 3, 0, 1045, "28000", fail_str);
}else {
/** Send error 1045 to client */
fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user,
dcb->remote,
(char*)((MYSQL_session *)dcb->data)->client_sha1,
(char*)((MYSQL_session *)dcb->data)->db,auth_val);
modutil_send_mysql_err_packet(dcb, 3, 0, 1045, "28000", fail_str);
}
if (fail_str)
free(fail_str);
LOGIF(LD, (skygw_log_write(
LOGFILE_DEBUG,
"%lu [gw_read_client_event] after "
"gw_mysql_do_authentication, fd %d, "
"state = MYSQL_AUTH_FAILED.",
protocol->owner_dcb->fd,
pthread_self())));
/**
* Release MYSQL_session since it is not used anymore.
*/
if (!DCB_IS_CLONE(dcb))
{
free(dcb->data);
}
dcb->data = NULL;
dcb_close(dcb);
}
read_buffer = gwbuf_consume(read_buffer, nbytes_read);
}
break;
case MYSQL_IDLE:
{
uint8_t* payload = NULL;
@ -944,12 +1208,13 @@ return_rc:
return rc;
}
///////////////////////////////////////////////
// client write event to Client triggered by EPOLLOUT
//////////////////////////////////////////////
/**
/**
* @node Client's fd became writable, and EPOLLOUT event
* arrived. As a consequence, client input buffer (writeq) is flushed.
* arrived. As a consequence, client input buffer (writeq) is flushed.
*
* Parameters:
* @param dcb - in, use
@ -957,7 +1222,7 @@ return_rc:
*
* @return constantly 1
*
*
*
* @details (write detailed description here)
*
*/
@ -967,6 +1232,53 @@ int gw_write_client_event(DCB *dcb)
CHK_DCB(dcb);
ss_dassert(dcb->state != DCB_STATE_DISCONNECTED);
if (dcb == NULL) {
goto return_1;
}
if (dcb->state == DCB_STATE_DISCONNECTED) {
goto return_1;
}
if (dcb->protocol == NULL) {
goto return_1;
}
protocol = (MySQLProtocol *)dcb->protocol;
CHK_PROTOCOL(protocol);
if (protocol->protocol_auth_state == MYSQL_IDLE)
{
dcb_drain_writeq(dcb);
goto return_1;
}
return_1:
#if defined(SS_DEBUG)
if (dcb->state == DCB_STATE_POLLING ||
dcb->state == DCB_STATE_NOPOLLING ||
dcb->state == DCB_STATE_ZOMBIE)
{
CHK_PROTOCOL(protocol);
}
#endif
return 1;
}
/**
* EPOLLOUT event arrived and as a consequence, client input buffer (writeq) is
* flushed. The data is encrypted and SSL is used. The SSL handshake must have
* been successfully completed prior to this function being called.
* @param client dcb
* @return constantly 1
*/
int gw_write_client_event_SSL(DCB *dcb)
{
MySQLProtocol *protocol = NULL;
CHK_DCB(dcb);
ss_dassert(dcb->state != DCB_STATE_DISCONNECTED);
if (dcb == NULL) {
@ -985,7 +1297,7 @@ int gw_write_client_event(DCB *dcb)
if (protocol->protocol_auth_state == MYSQL_IDLE)
{
dcb_drain_writeq(dcb);
dcb_drain_writeq_SSL(dcb);
goto return_1;
}
@ -1609,61 +1921,79 @@ return_rc:
return rc;
}
/**
* Create a character array including the query string.
* GWBUF given as input includes either one complete or partial query.
* Length of buffer is at most the query length+4 (length of packet header).
* Do the SSL authentication handshake.
* This creates the DCB SSL structure if one has not been created and starts the
* SSL handshake handling.
* @param protocol Protocol to connect with SSL
* @return 1 on success, 0 when the handshake is ongoing or -1 on error
*/
#if defined(NOT_USED)
static char* gw_get_or_create_querystr (
void* data,
bool* new_allocation)
int do_ssl_accept(MySQLProtocol* protocol)
{
GWBUF* buf = (GWBUF *)data;
size_t buflen; /*< first gw buffer data length */
size_t packetlen; /*< length of mysql packet */
size_t querylen; /*< total buffer length-<length of type indicator> */
size_t nbytes_copied;
char* startpos; /*< first byte of query in gw buffer */
char* str; /*< resulting query string */
CHK_GWBUF(buf);
packetlen = MYSQL_GET_PACKET_LEN((uint8_t *)GWBUF_DATA(buf));
str = (char *)malloc(packetlen); /*< leave space for terminating null */
if (str == NULL)
{
goto return_str;
}
*new_allocation = true;
/**
* First buffer includes 4 bytes header and a type indicator byte.
*/
buflen = GWBUF_LENGTH(buf);
querylen = packetlen-1;
ss_dassert(buflen<=querylen+5); /*< 5 == header+type indicator */
startpos = (char *)GWBUF_DATA(buf)+5;
nbytes_copied = MIN(querylen, buflen-5);
memcpy(str, startpos, nbytes_copied);
memset(&str[querylen-1], 0, 1);
buf = gwbuf_consume(buf, querylen-1);
/**
* In case of multi-packet statement whole buffer consists of query
* string.
*/
while (buf != NULL)
{
buflen = GWBUF_LENGTH(buf);
memcpy(str+nbytes_copied, GWBUF_DATA(buf), buflen);
nbytes_copied += buflen;
buf = gwbuf_consume(buf, buflen);
}
ss_dassert(str[querylen-1] == 0);
return_str:
return str;
}
int rval,errnum;
char errbuf[2014];
DCB* dcb = protocol->owner_dcb;
if(dcb->ssl == NULL)
{
if(dcb_create_SSL(dcb) != 0)
{
return -1;
}
}
rval = dcb_accept_SSL(dcb);
switch(rval)
{
case 0:
/** Not all of the data has been read. Go back to the poll
queue and wait for more.*/
rval = 0;
skygw_log_write_flush(LT,"SSL_accept ongoing for %s@%s",
protocol->owner_dcb->user,
protocol->owner_dcb->remote);
return 0;
break;
case 1:
spinlock_acquire(&protocol->protocol_lock);
protocol->protocol_auth_state = MYSQL_AUTH_SSL_HANDSHAKE_DONE;
protocol->use_ssl = true;
spinlock_release(&protocol->protocol_lock);
spinlock_acquire(&dcb->authlock);
dcb->func.write = gw_MySQLWrite_client_SSL;
dcb->func.write_ready = gw_write_client_event_SSL;
spinlock_release(&dcb->authlock);
rval = 1;
skygw_log_write_flush(LT,"SSL_accept done for %s@%s",
protocol->owner_dcb->user,
protocol->owner_dcb->remote);
break;
case -1:
spinlock_acquire(&protocol->protocol_lock);
protocol->protocol_auth_state = MYSQL_AUTH_SSL_HANDSHAKE_FAILED;
spinlock_release(&protocol->protocol_lock);
rval = -1;
skygw_log_write_flush(LE,
"Error: Fatal error in SSL_accept for %s",
protocol->owner_dcb->remote);
break;
default:
skygw_log_write_flush(LE,
"Error: Fatal error in SSL_accept, returned value was %d.",
rval);
break;
}
#ifdef SS_DEBUG
skygw_log_write(LD,"[do_ssl_accept] Protocol state: %s",
gw_mysql_protocol_state2string(protocol->protocol_auth_state));
#endif
return rval;
}

View File

@ -138,7 +138,7 @@ void mysql_protocol_done (
goto retblock;
}
scmd = p->protocol_cmd_history;
while (scmd != NULL)
{
scmd2 = scmd->scom_next;
@ -908,7 +908,11 @@ gw_mysql_protocol_state2string (int state) {
case MYSQL_AUTH_FAILED:
return "MySQL Authentication failed";
case MYSQL_IDLE:
return "MySQL authentication is succesfully done.";
return "MySQL authentication is succesfully done.";
case MYSQL_AUTH_SSL_REQ: return "MYSQL_AUTH_SSL_REQ";
case MYSQL_AUTH_SSL_HANDSHAKE_DONE: return "MYSQL_AUTH_SSL_HANDSHAKE_DONE";
case MYSQL_AUTH_SSL_HANDSHAKE_FAILED: return "MYSQL_AUTH_SSL_HANDSHAKE_FAILED";
case MYSQL_AUTH_SSL_HANDSHAKE_ONGOING: return "MYSQL_AUTH_SSL_HANDSHAKE_ONGOING";
default:
return "MySQL (unknown protocol state)";
}
@ -2217,7 +2221,8 @@ char *create_auth_fail_str(
char *username,
char *hostaddr,
char *sha1,
char *db)
char *db,
int errcode)
{
char* errstr;
const char* ferrstr;
@ -2232,6 +2237,10 @@ char *create_auth_fail_str(
{
ferrstr = "Access denied for user '%s'@'%s' (using password: %s) to database '%s'";
}
else if(errcode == MYSQL_FAILED_AUTH_SSL)
{
ferrstr = "Access without SSL denied";
}
else
{
ferrstr = "Access denied for user '%s'@'%s' (using password: %s)";
@ -2251,6 +2260,10 @@ char *create_auth_fail_str(
{
sprintf(errstr, ferrstr, username, hostaddr, (*sha1 == '\0' ? "NO" : "YES"), db);
}
else if(errcode == MYSQL_FAILED_AUTH_SSL)
{
sprintf(errstr, ferrstr);
}
else
{
sprintf(errstr, ferrstr, username, hostaddr, (*sha1 == '\0' ? "NO" : "YES"));

View File

@ -0,0 +1,11 @@
configure_file(test_ssl.sh ${CMAKE_CURRENT_BINARY_DIR}/test_ssl.sh @ONLY)
configure_file(no_ca.cnf ${CMAKE_CURRENT_BINARY_DIR}/no_ca.cnf @ONLY)
configure_file(no_server_cert.cnf ${CMAKE_CURRENT_BINARY_DIR}/no_server_cert.cnf @ONLY)
configure_file(no_server_key.cnf ${CMAKE_CURRENT_BINARY_DIR}/no_server_key.cnf @ONLY)
configure_file(bad_ca.cnf ${CMAKE_CURRENT_BINARY_DIR}/bad_ca.cnf @ONLY)
configure_file(bad_cert.cnf ${CMAKE_CURRENT_BINARY_DIR}/bad_cert.cnf @ONLY)
configure_file(bad_key.cnf ${CMAKE_CURRENT_BINARY_DIR}/bad_key.cnf @ONLY)
configure_file(bad_ssl.cnf ${CMAKE_CURRENT_BINARY_DIR}/bad_ssl.cnf @ONLY)
configure_file(bad_ssl_version.cnf ${CMAKE_CURRENT_BINARY_DIR}/bad_ssl_version.cnf @ONLY)
configure_file(ok.cnf ${CMAKE_CURRENT_BINARY_DIR}/ok.cnf @ONLY)
add_test(NAME SSLTest COMMAND ${CMAKE_CURRENT_BINARY_DIR}/test_ssl.sh)

View File

@ -0,0 +1,28 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=enabled
ssl_ca_cert=This is not a value
ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert
ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,28 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=enabled
ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca
ssl_cert=This is not a value
ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,28 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=enabled
ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca
ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert
ssl_key=This is not a value
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,28 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=testing
ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca
ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert
ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,29 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=enabled
ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca
ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert
ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key
ssl_version=Don't use SSL, it's not needed!
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,28 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=enabled
#ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca
ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert
ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,28 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=enabled
ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca
#ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert
ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,28 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=enabled
ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca
ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert
#ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,28 @@
[maxscale]
threads=1
logdir=@CMAKE_CURRENT_BINARY_DIR@
datadir=@CMAKE_CURRENT_BINARY_DIR@
piddir=@CMAKE_CURRENT_BINARY_DIR@
cachedir=@CMAKE_CURRENT_BINARY_DIR@
[Testservice]
type=service
router=readconnroute
servers=server1
user=user
passwd=pwd
ssl=enabled
ssl_ca_cert=@CMAKE_CURRENT_BINARY_DIR@/ca
ssl_cert=@CMAKE_CURRENT_BINARY_DIR@/server-cert
ssl_key=@CMAKE_CURRENT_BINARY_DIR@/server-key
[Testlistener]
type=listener
service=Testservice
protocol=MySQLBackend
port=12345
[server1]
type=server
address=127.0.0.1
port=4321

View File

@ -0,0 +1,83 @@
#!/usr/bin/env bash
function create_certs()
{
echo "CA cert" > @CMAKE_CURRENT_BINARY_DIR@/ca.pem
echo "Server Certificate" > @CMAKE_CURRENT_BINARY_DIR@/server-cert.pem
echo "Server Key" > @CMAKE_CURRENT_BINARY_DIR@/server-key.pem
}
function start_maxscale ()
{
local result=$(@CMAKE_INSTALL_PREFIX@/@MAXSCALE_BINDIR@/maxscale -d -f $1 &> $1.log;echo $?)
if [[ $result == "0" ]]
then
echo "Error: $1 exited with status $result!"
exit 1
fi
}
# All test cases expect that MaxScale will not start with a bad configuration or missing certificates
# No CA defined
printf "Testing No CA defined"
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/no_ca.cnf
echo " OK"
# No cert defined
printf "Testing No cert defined"
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/no_cert.cnf
echo " OK"
# No key defined
printf "Testing No key defined"
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/no_key.cnf
echo " OK"
# Bad SSL value defined
printf "Testing Bad SSL defined"
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/bad_ssl.cnf
echo " OK"
# Bad SSL version defined
printf "Testing Bad SSL version defined"
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/bad_ssl_version.cnf
echo " OK"
# Bad CA value defined
printf "Testing Bad CA defined"
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/bad_ca.cnf
echo " OK"
# Bad server certificate defined
printf "Testing Bad cert defined"
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/bad_cert.cnf
echo " OK"
# Bad server key defined
printf "Testing Bad key defined"
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/bad_key.cnf
echo " OK"
# No CA file
printf "Testing No CA file"
create_certs
rm @CMAKE_CURRENT_BINARY_DIR@/ca.pem
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/ok.cnf
echo " OK"
# No server certificate file
printf "Testing No cert file"
create_certs
rm @CMAKE_CURRENT_BINARY_DIR@/server-cert.pem
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/ok.cnf
echo " OK"
# No server key file
printf "Testing No key file"
create_certs
rm @CMAKE_CURRENT_BINARY_DIR@/server-key.pem
start_maxscale @CMAKE_CURRENT_BINARY_DIR@/ok.cnf
echo " OK"
exit 0