mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-03-11 05:48:41 +00:00
MAJOR: threads/ssl: Make SSL part thread-safe
First, OpenSSL is now initialized to be thread-safe. This is done by setting 2 callbacks. The first one is ssl_locking_function. It handles the locks and unlocks. The second one is ssl_id_function. It returns the current thread id. During the init step, we create as much as R/W locks as needed, ie the number returned by CRYPTO_num_locks function. Next, The reusable SSL session in the server context is now thread-local. Shctx is now also initialized if HAProxy is started with several threads. And finally, a global lock has been added to protect the LRU cache used to store generated certificates. The function ssl_sock_get_generated_cert is now deprecated because the retrieved certificate can be removed by another threads in same time. Instead, a new function has been added, ssl_sock_assign_generated_cert. It must be used to search a certificate in the cache and set it immediatly if found.
This commit is contained in:
parent
6b35e9bfbf
commit
821bb9beaa
@ -158,6 +158,8 @@ enum lock_label {
|
||||
PEER_LOCK,
|
||||
BUF_WQ_LOCK,
|
||||
STRMS_LOCK,
|
||||
SSL_LOCK,
|
||||
SSL_GEN_CERTS_LOCK,
|
||||
LOCK_LABELS
|
||||
};
|
||||
struct lock_stat {
|
||||
@ -244,7 +246,7 @@ static inline void show_lock_stats()
|
||||
"TASK_RQ", "TASK_WQ", "POOL",
|
||||
"LISTENER", "LISTENER_QUEUE", "PROXY", "SERVER",
|
||||
"UPDATED_SERVERS", "LBPRM", "SIGNALS", "STK_TABLE", "STK_SESS",
|
||||
"APPLETS", "PEER", "BUF_WQ", "STREAMS" };
|
||||
"APPLETS", "PEER", "BUF_WQ", "STREAMS", "SSL", "SSL_GEN_CERTS"};
|
||||
int lbl;
|
||||
|
||||
for (lbl = 0; lbl < LOCK_LABELS; lbl++) {
|
||||
|
@ -72,6 +72,7 @@ void ssl_free_dh(void);
|
||||
void ssl_free_engines(void);
|
||||
|
||||
SSL_CTX *ssl_sock_create_cert(struct connection *conn, const char *servername, unsigned int key);
|
||||
SSL_CTX *ssl_sock_assign_generated_cert(unsigned int key, struct bind_conf *bind_conf, SSL *ssl);
|
||||
SSL_CTX *ssl_sock_get_generated_cert(unsigned int key, struct bind_conf *bind_conf);
|
||||
int ssl_sock_set_generated_cert(SSL_CTX *ctx, unsigned int key, struct bind_conf *bind_conf);
|
||||
unsigned int ssl_sock_generated_cert_key(const void *data, size_t len);
|
||||
|
@ -273,7 +273,7 @@ struct server {
|
||||
char *sni_expr; /* Temporary variable to store a sample expression for SNI */
|
||||
struct {
|
||||
SSL_CTX *ctx;
|
||||
SSL_SESSION *reused_sess;
|
||||
SSL_SESSION **reused_sess;
|
||||
char *ciphers; /* cipher suite to use if non-null */
|
||||
int options; /* ssl options */
|
||||
struct tls_version_filter methods; /* ssl methods */
|
||||
|
142
src/ssl_sock.c
142
src/ssl_sock.c
@ -206,6 +206,51 @@ static struct {
|
||||
.capture_cipherlist = 0,
|
||||
};
|
||||
|
||||
#ifdef USE_THREAD
|
||||
static HA_RWLOCK_T *ssl_rwlocks;
|
||||
|
||||
|
||||
unsigned long ssl_id_function(void)
|
||||
{
|
||||
return (unsigned long)tid;
|
||||
}
|
||||
|
||||
void ssl_locking_function(int mode, int n, const char * file, int line)
|
||||
{
|
||||
if (mode & CRYPTO_LOCK) {
|
||||
if (mode & CRYPTO_READ)
|
||||
RWLOCK_RDLOCK(SSL_LOCK, &ssl_rwlocks[n]);
|
||||
else
|
||||
RWLOCK_WRLOCK(SSL_LOCK, &ssl_rwlocks[n]);
|
||||
}
|
||||
else {
|
||||
if (mode & CRYPTO_READ)
|
||||
RWLOCK_RDUNLOCK(SSL_LOCK, &ssl_rwlocks[n]);
|
||||
else
|
||||
RWLOCK_WRUNLOCK(SSL_LOCK, &ssl_rwlocks[n]);
|
||||
}
|
||||
}
|
||||
|
||||
static int ssl_locking_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
ssl_rwlocks = malloc(sizeof(HA_RWLOCK_T)*CRYPTO_num_locks());
|
||||
if (!ssl_rwlocks)
|
||||
return -1;
|
||||
|
||||
for (i = 0 ; i < CRYPTO_num_locks() ; i++)
|
||||
RWLOCK_INIT(&ssl_rwlocks[i]);
|
||||
|
||||
CRYPTO_set_id_callback(ssl_id_function);
|
||||
CRYPTO_set_locking_callback(ssl_locking_function);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/* This memory pool is used for capturing clienthello parameters. */
|
||||
struct ssl_capture {
|
||||
unsigned long long int xxh64;
|
||||
@ -257,6 +302,12 @@ static char *x509v3_ext_values[X509V3_EXT_SIZE] = {
|
||||
/* LRU cache to store generated certificate */
|
||||
static struct lru64_head *ssl_ctx_lru_tree = NULL;
|
||||
static unsigned int ssl_ctx_lru_seed = 0;
|
||||
static unsigned int ssl_ctx_serial;
|
||||
|
||||
#ifdef USE_THREAD
|
||||
static HA_RWLOCK_T ssl_ctx_lru_rwlock;
|
||||
#endif
|
||||
|
||||
#endif // SSL_CTRL_SET_TLSEXT_HOSTNAME
|
||||
|
||||
static struct ssl_bind_kw ssl_bind_kws[];
|
||||
@ -1588,8 +1639,6 @@ static int ssl_sock_advertise_alpn_protos(SSL *s, const unsigned char **out,
|
||||
static SSL_CTX *
|
||||
ssl_sock_do_create_cert(const char *servername, struct bind_conf *bind_conf, SSL *ssl)
|
||||
{
|
||||
static unsigned int serial = 0;
|
||||
|
||||
X509 *cacert = bind_conf->ca_sign_cert;
|
||||
EVP_PKEY *capkey = bind_conf->ca_sign_pkey;
|
||||
SSL_CTX *ssl_ctx = NULL;
|
||||
@ -1621,9 +1670,7 @@ ssl_sock_do_create_cert(const char *servername, struct bind_conf *bind_conf, SSL
|
||||
* number */
|
||||
if (X509_set_version(newcrt, 2L) != 1)
|
||||
goto mkcert_error;
|
||||
if (!serial)
|
||||
serial = now_ms;
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(newcrt), serial++);
|
||||
ASN1_INTEGER_set(X509_get_serialNumber(newcrt), HA_ATOMIC_ADD(&ssl_ctx_serial, 1));
|
||||
|
||||
/* Set duration for the certificate */
|
||||
if (!X509_gmtime_adj(X509_get_notBefore(newcrt), (long)-60*60*24) ||
|
||||
@ -1742,20 +1789,36 @@ ssl_sock_create_cert(struct connection *conn, const char *servername, unsigned i
|
||||
}
|
||||
|
||||
/* Do a lookup for a certificate in the LRU cache used to store generated
|
||||
* certificates. */
|
||||
* certificates and immediately assign it to the SSL session if not null. */
|
||||
SSL_CTX *
|
||||
ssl_sock_get_generated_cert(unsigned int key, struct bind_conf *bind_conf)
|
||||
ssl_sock_assign_generated_cert(unsigned int key, struct bind_conf *bind_conf, SSL *ssl)
|
||||
{
|
||||
struct lru64 *lru = NULL;
|
||||
|
||||
if (ssl_ctx_lru_tree) {
|
||||
RWLOCK_RDLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
|
||||
lru = lru64_lookup(key, ssl_ctx_lru_tree, bind_conf->ca_sign_cert, 0);
|
||||
if (lru && lru->domain)
|
||||
if (lru && lru->domain) {
|
||||
if (ssl)
|
||||
SSL_set_SSL_CTX(ssl, (SSL_CTX *)lru->data);
|
||||
RWLOCK_RDUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
|
||||
return (SSL_CTX *)lru->data;
|
||||
}
|
||||
RWLOCK_RDUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Same as <ssl_sock_assign_generated_cert> but without SSL session. This
|
||||
* function is not thread-safe, it should only be used to check if a certificate
|
||||
* exists in the lru cache (with no warranty it will not be removed by another
|
||||
* thread). It is kept for backward compatibility. */
|
||||
SSL_CTX *
|
||||
ssl_sock_get_generated_cert(unsigned int key, struct bind_conf *bind_conf)
|
||||
{
|
||||
return ssl_sock_assign_generated_cert(key, bind_conf, NULL);
|
||||
}
|
||||
|
||||
/* Set a certificate int the LRU cache used to store generated
|
||||
* certificate. Return 0 on success, otherwise -1 */
|
||||
int
|
||||
@ -1764,12 +1827,16 @@ ssl_sock_set_generated_cert(SSL_CTX *ssl_ctx, unsigned int key, struct bind_conf
|
||||
struct lru64 *lru = NULL;
|
||||
|
||||
if (ssl_ctx_lru_tree) {
|
||||
RWLOCK_WRLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
|
||||
lru = lru64_get(key, ssl_ctx_lru_tree, bind_conf->ca_sign_cert, 0);
|
||||
if (!lru)
|
||||
if (!lru) {
|
||||
RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
|
||||
return -1;
|
||||
}
|
||||
if (lru->domain && lru->data)
|
||||
lru->free((SSL_CTX *)lru->data);
|
||||
lru64_commit(lru, ssl_ctx, bind_conf->ca_sign_cert, 0, (void (*)(void *))SSL_CTX_free);
|
||||
RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
@ -1795,6 +1862,7 @@ ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind_con
|
||||
|
||||
key = ssl_sock_generated_cert_key(servername, strlen(servername));
|
||||
if (ssl_ctx_lru_tree) {
|
||||
RWLOCK_WRLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
|
||||
lru = lru64_get(key, ssl_ctx_lru_tree, cacert, 0);
|
||||
if (lru && lru->domain)
|
||||
ssl_ctx = (SSL_CTX *)lru->data;
|
||||
@ -1803,6 +1871,7 @@ ssl_sock_generate_certificate(const char *servername, struct bind_conf *bind_con
|
||||
lru64_commit(lru, ssl_ctx, cacert, 0, (void (*)(void *))SSL_CTX_free);
|
||||
}
|
||||
SSL_set_SSL_CTX(ssl, ssl_ctx);
|
||||
RWLOCK_WRUNLOCK(SSL_GEN_CERTS_LOCK, &ssl_ctx_lru_rwlock);
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
@ -1818,18 +1887,13 @@ static int
|
||||
ssl_sock_generate_certificate_from_conn(struct bind_conf *bind_conf, SSL *ssl)
|
||||
{
|
||||
unsigned int key;
|
||||
SSL_CTX *ssl_ctx = NULL;
|
||||
struct connection *conn = SSL_get_app_data(ssl);
|
||||
|
||||
conn_get_to_addr(conn);
|
||||
if (conn->flags & CO_FL_ADDR_TO_SET) {
|
||||
key = ssl_sock_generated_cert_key(&conn->addr.to, get_addr_len(&conn->addr.to));
|
||||
ssl_ctx = ssl_sock_get_generated_cert(key, bind_conf);
|
||||
if (ssl_ctx) {
|
||||
/* switch ctx */
|
||||
SSL_set_SSL_CTX(ssl, ssl_ctx);
|
||||
if (ssl_sock_assign_generated_cert(key, bind_conf, ssl))
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -4351,7 +4415,15 @@ int ssl_sock_prepare_srv_ctx(struct server *srv)
|
||||
global.ssl_used_backend = 1;
|
||||
|
||||
/* Initiate SSL context for current server */
|
||||
srv->ssl_ctx.reused_sess = NULL;
|
||||
if (!srv->ssl_ctx.reused_sess) {
|
||||
if ((srv->ssl_ctx.reused_sess = calloc(1, global.nbthread*sizeof(SSL_SESSION*))) == NULL) {
|
||||
Alert("Proxy '%s', server '%s' [%s:%d] out of memory.\n",
|
||||
curproxy->id, srv->id,
|
||||
srv->conf.file, srv->conf.line);
|
||||
cfgerr++;
|
||||
return cfgerr;
|
||||
}
|
||||
}
|
||||
if (srv->use_ssl)
|
||||
srv->xprt = &ssl_sock;
|
||||
if (srv->check.use_ssl)
|
||||
@ -4599,7 +4671,10 @@ int ssl_sock_prepare_bind_conf(struct bind_conf *bind_conf)
|
||||
}
|
||||
}
|
||||
|
||||
alloc_ctx = shctx_init(&ssl_shctx, global.tune.sslcachesize, sizeof(struct sh_ssl_sess_hdr) + SHSESS_BLOCK_MIN_SIZE, sizeof(*sh_ssl_sess_tree), (!global_ssl.private_cache && (global.nbproc > 1)) ? 1 : 0);
|
||||
alloc_ctx = shctx_init(&ssl_shctx, global.tune.sslcachesize,
|
||||
sizeof(struct sh_ssl_sess_hdr) + SHSESS_BLOCK_MIN_SIZE,
|
||||
sizeof(*sh_ssl_sess_tree),
|
||||
((global.nbthread > 1) || (!global_ssl.private_cache && (global.nbproc > 1))) ? 1 : 0);
|
||||
if (alloc_ctx < 0) {
|
||||
if (alloc_ctx == SHCTX_E_INIT_LOCK)
|
||||
Alert("Unable to initialize the lock for the shared SSL session cache. You can retry using the global statement 'tune.ssl.force-private-cache' but it could increase CPU usage due to renegotiations if nbproc > 1.\n");
|
||||
@ -4706,9 +4781,12 @@ ssl_sock_load_ca(struct bind_conf *bind_conf)
|
||||
return err;
|
||||
|
||||
#if (defined SSL_CTRL_SET_TLSEXT_HOSTNAME && !defined SSL_NO_GENERATE_CERTIFICATES)
|
||||
if (global_ssl.ctx_cache)
|
||||
if (global_ssl.ctx_cache) {
|
||||
ssl_ctx_lru_tree = lru64_new(global_ssl.ctx_cache);
|
||||
RWLOCK_INIT(&ssl_ctx_lru_rwlock);
|
||||
}
|
||||
ssl_ctx_lru_seed = (unsigned int)time(NULL);
|
||||
ssl_ctx_serial = now_ms;
|
||||
#endif
|
||||
|
||||
if (!bind_conf->ca_sign_file) {
|
||||
@ -4826,9 +4904,9 @@ static int ssl_sock_init(struct connection *conn)
|
||||
|
||||
SSL_set_connect_state(conn->xprt_ctx);
|
||||
if (objt_server(conn->target)->ssl_ctx.reused_sess) {
|
||||
if(!SSL_set_session(conn->xprt_ctx, objt_server(conn->target)->ssl_ctx.reused_sess)) {
|
||||
SSL_SESSION_free(objt_server(conn->target)->ssl_ctx.reused_sess);
|
||||
objt_server(conn->target)->ssl_ctx.reused_sess = NULL;
|
||||
if(!SSL_set_session(conn->xprt_ctx, objt_server(conn->target)->ssl_ctx.reused_sess[tid])) {
|
||||
SSL_SESSION_free(objt_server(conn->target)->ssl_ctx.reused_sess[tid]);
|
||||
objt_server(conn->target)->ssl_ctx.reused_sess[tid] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5131,13 +5209,13 @@ reneg_ok:
|
||||
global.ssl_be_keys_max = global.ssl_be_keys_per_sec.curr_ctr;
|
||||
|
||||
/* check if session was reused, if not store current session on server for reuse */
|
||||
if (objt_server(conn->target)->ssl_ctx.reused_sess) {
|
||||
SSL_SESSION_free(objt_server(conn->target)->ssl_ctx.reused_sess);
|
||||
objt_server(conn->target)->ssl_ctx.reused_sess = NULL;
|
||||
if (objt_server(conn->target)->ssl_ctx.reused_sess[tid]) {
|
||||
SSL_SESSION_free(objt_server(conn->target)->ssl_ctx.reused_sess[tid]);
|
||||
objt_server(conn->target)->ssl_ctx.reused_sess[tid] = NULL;
|
||||
}
|
||||
|
||||
if (!(objt_server(conn->target)->ssl_ctx.options & SRV_SSL_O_NO_REUSE))
|
||||
objt_server(conn->target)->ssl_ctx.reused_sess = SSL_get1_session(conn->xprt_ctx);
|
||||
objt_server(conn->target)->ssl_ctx.reused_sess[tid] = SSL_get1_session(conn->xprt_ctx);
|
||||
}
|
||||
else {
|
||||
update_freq_ctr(&global.ssl_fe_keys_per_sec, 1);
|
||||
@ -5156,9 +5234,9 @@ reneg_ok:
|
||||
ERR_clear_error();
|
||||
|
||||
/* free resumed session if exists */
|
||||
if (objt_server(conn->target) && objt_server(conn->target)->ssl_ctx.reused_sess) {
|
||||
SSL_SESSION_free(objt_server(conn->target)->ssl_ctx.reused_sess);
|
||||
objt_server(conn->target)->ssl_ctx.reused_sess = NULL;
|
||||
if (objt_server(conn->target) && objt_server(conn->target)->ssl_ctx.reused_sess[tid]) {
|
||||
SSL_SESSION_free(objt_server(conn->target)->ssl_ctx.reused_sess[tid]);
|
||||
objt_server(conn->target)->ssl_ctx.reused_sess[tid] = NULL;
|
||||
}
|
||||
|
||||
/* Fail on all other handshake errors */
|
||||
@ -8629,6 +8707,9 @@ static void __ssl_sock_init(void)
|
||||
SSL_library_init();
|
||||
cm = SSL_COMP_get_compression_methods();
|
||||
sk_SSL_COMP_zero(cm);
|
||||
#ifdef USE_THREAD
|
||||
ssl_locking_init();
|
||||
#endif
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x1000200fL && !defined OPENSSL_NO_TLSEXT && !defined OPENSSL_IS_BORINGSSL && !defined LIBRESSL_VERSION_NUMBER)
|
||||
sctl_ex_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, ssl_sock_sctl_free_func);
|
||||
#endif
|
||||
@ -8740,7 +8821,10 @@ __attribute__((destructor))
|
||||
static void __ssl_sock_deinit(void)
|
||||
{
|
||||
#if (defined SSL_CTRL_SET_TLSEXT_HOSTNAME && !defined SSL_NO_GENERATE_CERTIFICATES)
|
||||
lru64_destroy(ssl_ctx_lru_tree);
|
||||
if (ssl_ctx_lru_tree) {
|
||||
lru64_destroy(ssl_ctx_lru_tree);
|
||||
RWLOCK_DESTROY(&ssl_ctx_lru_rwlock);
|
||||
}
|
||||
#endif
|
||||
|
||||
ERR_remove_state(0);
|
||||
|
Loading…
Reference in New Issue
Block a user