[Pgpool-hackers] [PATCH] Add OpenSSL support for both frontend and backend connections

Sean Finney sean at stickybit.se
Wed Jan 20 15:29:28 UTC 2010


The new pool_ssl.c provides the necessary functions for negotiating
SSL based connections for both frontend and backend connections.  child.c
has been updated to request the negotiation in both cases, and pool_stream.c
has been modified to use SSL based i/o functions if a connection has
successfully negotiated a connection.

SSL related failures should be handled as gracefully as possible.  For
frontend connections, the client is allowed to continue in cleartext
in the case that SSL negotiation was not successful.  For backend
connections, plaintext connections should continue as well, modulo
any pg_hba restrictions on the remote server.  It is also possible to
have connections where only one of the frontend/backend uses SSL.

By default this functionality is off (further testing should be done
and perhaps better controls on certificate verification, etc).

New fields are added to the global pool configuration to allow specifying
SSL related settings:

 * "ssl", global on/off switch (default: off)
 * "ssl_cert", path to SSL public certificate (default: "")
 * "ssl_key", path to SSL private key (default: "")

The sample pgpool.conf files have been updated with comments and sample
values for these new settings.

The autoconf build system has been updated with a new "--with-openssl" option
to allow enabling/disabling SSL support at build time.  For simplicity and
clarity no effort is made to provide extra flexibility similar to the existing
--with-pgsql{,-include-dir,lib-dir} ./configure flags.  In the meantime it's
expected that the necessary flags are provided via CFLAGS/LDFLAGS etc when
calling ./configure.
---
 Makefile.am                     |    2 +-
 child.c                         |   24 +----
 configure.in                    |   19 ++++
 main.c                          |    6 ++
 pgpool.conf.sample              |   10 ++
 pgpool.conf.sample-master-slave |    9 ++
 pgpool.conf.sample-replication  |    9 ++
 pool.h                          |   24 +++++
 pool_config.c                   |   50 +++++++++++
 pool_ssl.c                      |  180 +++++++++++++++++++++++++++++++++++++++
 pool_stream.c                   |   30 ++++++-
 11 files changed, 339 insertions(+), 24 deletions(-)
 create mode 100644 pool_ssl.c

diff --git a/Makefile.am b/Makefile.am
index b2c727b..c692ee3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -11,7 +11,7 @@ pgpool_SOURCES = pool.h pool_type.h version.h pgpool.conf.sample \
 	pool_query_cache.c pool_hba.conf.sample sample/pgpool.pam\
 	pool_hba.c pool_path.h pool_path.c pool_ip.h pool_ip.c pool_type.h \
 	ps_status.c strlcpy.c recovery.c pool_relcache.c pool_process_reporting.c \
-	pool_timestamp.c pool_timestamp.h \
+	pool_ssl.c pool_timestamp.c pool_timestamp.h \
 	pool_proto_modules.c pool_proto_modules.h
 
 pg_md5_SOURCES = pg_md5.c md5.c md5.h
diff --git a/child.c b/child.c
index 78c466f..8e7ea5a 100644
--- a/child.c
+++ b/child.c
@@ -169,7 +169,6 @@ void do_child(int unix_fd, int inet_fd)
 	for (;;)
 	{
 		int connection_reuse = 1;
-		int ssl_request = 0;
 		StartupPacket *sp;
 
 		idle = 1;
@@ -235,25 +234,10 @@ void do_child(int unix_fd, int inet_fd)
 		}
 
 		/* SSL? */
-		if (sp->major == 1234 && sp->minor == 5679)
+		if (sp->major == 1234 && sp->minor == 5679 && !frontend->ssl_active)
 		{
-			/* SSL not supported */
-			pool_debug("SSLRequest: sent N; retry startup");
-			if (ssl_request)
-			{
-				pool_close(frontend);
-				pool_free_startup_packet(sp);
-				connection_count_down();
-				continue;
-			}
-
-			/*
-			 * say to the frontend "we do not suppport SSL"
-			 * note that this is not a NOTICE response despite it's an 'N'!
-			 */
-			pool_write_and_flush(frontend, "N", 1);
-			ssl_request = 1;
-			pool_free_startup_packet(sp);
+			pool_debug("SSLRequest from client");
+			pool_ssl_negotiate_serverclient(frontend);
 			goto retry_startup;
 		}
 
@@ -1167,6 +1151,7 @@ static POOL_CONNECTION_POOL *connect_backend(StartupPacket *sp, POOL_CONNECTION
 
 			/* mark this is a backend connection */
 			CONNECTION(backend, i)->isbackend = 1;
+			pool_ssl_negotiate_clientserver(CONNECTION(backend, i));
 
 			/*
 			 * save startup packet info
@@ -1434,6 +1419,7 @@ POOL_CONNECTION_POOL_SLOT *make_persistent_db_connection(
 	cp->con = pool_open(fd);
 	cp->closetime = 0;
 	cp->con->isbackend = 1;
+	pool_ssl_negotiate_clientserver(cp->con);
 
 	/*
 	 * build V3 startup packet
diff --git a/configure.in b/configure.in
index 90f3513..5d61d9c 100644
--- a/configure.in
+++ b/configure.in
@@ -275,6 +275,25 @@ AC_ARG_WITH(pgsql-libdir,
 	PGSQL_LIB_DIR="$withval"
     ])
 
+
+AC_ARG_WITH(openssl,
+    [  --with-openssl     build with OpenSSL support],
+    [],
+    [with_openssl=auto])
+
+if test "$with_openssl" = yes || test "$with_openssl" = auto; then
+    AC_CHECK_HEADERS(openssl/ssl.h,
+        [AC_DEFINE([USE_SSL], 1,
+            [Define to 1 to build with SSL support. (--with-openssl)])],
+	[
+            if test "$with_openssl" = yes; then
+                AC_MSG_ERROR([header file <openssl/ssl.h> is required for SSL])
+            else
+                AC_MSG_WARN([header file <openssl/ssl.h> is required for SSL])
+            fi
+        ])
+fi
+
 AC_ARG_WITH(pam,
     [  --with-pam     build with PAM support],
     [AC_DEFINE([USE_PAM], 1, [Define to 1 to build with PAM support. (--with-pam)])])
diff --git a/main.c b/main.c
index 360bdb0..86c285f 100644
--- a/main.c
+++ b/main.c
@@ -266,6 +266,12 @@ int main(int argc, char **argv)
 		}
 	}
 
+#ifdef USE_SSL
+	/* global ssl init */
+	SSL_library_init();
+	SSL_load_error_strings();
+#endif /* USE_SSL */
+
 	mypid = getpid();
 
 	if (pool_init_config())
diff --git a/pgpool.conf.sample b/pgpool.conf.sample
index e94fc37..f709e03 100644
--- a/pgpool.conf.sample
+++ b/pgpool.conf.sample
@@ -214,3 +214,13 @@ recovery_timeout = 90
 # explicit transactions!)  0 means no disconnect. This parameter only
 # takes effect in recovery 2nd stage.
 client_idle_limit_in_recovery = 0
+
+# If true, enable SSL support for both frontend and backend connections.
+# note that you must also set ssl_key and ssl_cert for SSL to work in
+# the frontend connections.
+ssl = false
+# path to the SSL private key file
+#ssl_key = './server.key'
+# path to the SSL public certificate file
+#ssl_key = './server.cert'
+
diff --git a/pgpool.conf.sample-master-slave b/pgpool.conf.sample-master-slave
index fb7b0d5..490514f 100644
--- a/pgpool.conf.sample-master-slave
+++ b/pgpool.conf.sample-master-slave
@@ -214,3 +214,12 @@ recovery_timeout = 90
 # explicit transactions!)  0 means no disconnect. This parameter only
 # takes effect in recovery 2nd stage.
 client_idle_limit_in_recovery = 0
+
+# If true, enable SSL support for both frontend and backend connections.
+# note that you must also set ssl_key and ssl_cert for SSL to work in
+# the frontend connections.
+ssl = false
+# path to the SSL private key file
+#ssl_key = './server.key'
+# path to the SSL public certificate file
+#ssl_key = './server.cert'
diff --git a/pgpool.conf.sample-replication b/pgpool.conf.sample-replication
index e109652..8483e08 100644
--- a/pgpool.conf.sample-replication
+++ b/pgpool.conf.sample-replication
@@ -214,3 +214,12 @@ recovery_timeout = 90
 # explicit transactions!)  0 means no disconnect. This parameter only
 # takes effect in recovery 2nd stage.
 client_idle_limit_in_recovery = 0
+
+# If true, enable SSL support for both frontend and backend connections.
+# note that you must also set ssl_key and ssl_cert for SSL to work in
+# the frontend connections.
+ssl = false
+# path to the SSL private key file
+#ssl_key = './server.key'
+# path to the SSL public certificate file
+#ssl_key = './server.cert'
diff --git a/pool.h b/pool.h
index ae49ccc..9657bee 100644
--- a/pool.h
+++ b/pool.h
@@ -35,6 +35,12 @@
 #include <sys/types.h>
 #include <limits.h>
 
+#ifdef USE_SSL
+#include <openssl/crypto.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#endif
+
 /* undef this if you have problems with non blocking accept() */
 #define NONE_BLOCK
 
@@ -208,6 +214,11 @@ typedef struct {
 	int replication_enabled;		/* replication mode enabled */
 	int master_slave_enabled;		/* master/slave mode enabled */
 	int num_reset_queries;		/* number of queries in reset_query_list */
+
+	/* ssl configuration */
+	int ssl;	/* if non 0, activate ssl support (frontend+backend) */
+	char *ssl_cert;	/* path to ssl certificate (frontend only) */
+	char *ssl_key;	/* path to ssl key (frontend only) */
 } POOL_CONFIG;
 
 #define MAX_PASSWORD_SIZE		1024
@@ -228,6 +239,12 @@ typedef struct {
 	int wbufsz;	/* write buffer size */
 	int wbufpo;	/* buffer offset */
 
+#ifdef USE_SSL
+	SSL_CTX *ssl_ctx; /* SSL connection context */
+	SSL *ssl;	/* SSL connection */
+#endif
+	int ssl_active; /* SSL is failed if < 0, off if 0, on if > 0 */
+
 	char *hp;	/* pending data buffer head address */
 	int po;		/* pending data offset */
 	int bufsz;	/* pending data buffer size */
@@ -530,6 +547,13 @@ extern POOL_CONNECTION_POOL *pool_get_cp(char *user, char *database, int protoMa
 extern void pool_discard_cp(char *user, char *database, int protoMajor);
 extern void pool_backend_timer(void);
 
+/* SSL functionality */
+extern void pool_ssl_negotiate_serverclient(POOL_CONNECTION *cp);
+extern void pool_ssl_negotiate_clientserver(POOL_CONNECTION *cp);
+extern void pool_ssl_close(POOL_CONNECTION *cp);
+extern int pool_ssl_read(POOL_CONNECTION *cp, void *buf, int size);
+extern int pool_ssl_write(POOL_CONNECTION *cp, const void *buf, int size);
+
 extern POOL_STATUS ErrorResponse(POOL_CONNECTION *frontend, 
 								  POOL_CONNECTION_POOL *backend);
 
diff --git a/pool_config.c b/pool_config.c
index 3b76f3b..94c626a 100644
--- a/pool_config.c
+++ b/pool_config.c
@@ -1901,6 +1901,9 @@ int pool_init_config(void)
     pool_config->recovery_2nd_stage_command = "";
 	pool_config->recovery_timeout = 90;
 	pool_config->client_idle_limit_in_recovery = 0;
+	pool_config->ssl = 0;
+	pool_config->ssl_cert = "";
+	pool_config->ssl_key = "";
 
 	res = gethostname(localhostname,sizeof(localhostname));
 	if(res !=0 )
@@ -2937,6 +2940,53 @@ int pool_get_config(char *confpath, POOL_CONFIG_CONTEXT context)
 			}
 			pool_config->log_statement = v;
 		}
+        else if (!strcmp(key, "ssl") && CHECK_CONTEXT(INIT_CONFIG, context))
+		{
+			int v = eval_logical(yytext);
+
+			if (v < 0)
+			{
+				pool_error("pool_config: invalid value %s for %s", yytext, key);
+				return(-1);
+			}
+			pool_config->ssl = v;
+		}
+	else if (!strcmp(key, "ssl_cert") && CHECK_CONTEXT(INIT_CONFIG, context))
+		{
+			char *str;
+
+			if (token != POOL_STRING && token != POOL_UNQUOTED_STRING && token != POOL_KEY)
+			{
+				PARSE_ERROR();
+				fclose(fd);
+				return(-1);
+			}
+			str = extract_string(yytext, token);
+			if (str == NULL)
+			{
+				fclose(fd);
+				return(-1);
+			}
+			pool_config->ssl_cert = str;
+		}
+	else if (!strcmp(key, "ssl_key") && CHECK_CONTEXT(INIT_CONFIG, context))
+		{
+			char *str;
+
+			if (token != POOL_STRING && token != POOL_UNQUOTED_STRING && token != POOL_KEY)
+			{
+				PARSE_ERROR();
+				fclose(fd);
+				return(-1);
+			}
+			str = extract_string(yytext, token);
+			if (str == NULL)
+			{
+				fclose(fd);
+				return(-1);
+			}
+			pool_config->ssl_key = str;
+		}
 	}
 
 	fclose(fd);
diff --git a/pool_ssl.c b/pool_ssl.c
new file mode 100644
index 0000000..9de5616
--- /dev/null
+++ b/pool_ssl.c
@@ -0,0 +1,180 @@
+/* -*-pgsql-c-*- */
+/*
+ * $Header: $
+ *
+ * pgpool: a language independent connection pool server for PostgreSQL
+ * written by Tatsuo Ishii
+ *
+ * Copyright (c) 2003-2009	PgPool Global Development Group
+ *
+ * Permission to use, copy, modify, and distribute this software and
+ * its documentation for any purpose and without fee is hereby
+ * granted, provided that the above copyright notice appear in all
+ * copies and that both that copyright notice and this permission
+ * notice appear in supporting documentation, and that the name of the
+ * author not be used in advertising or publicity pertaining to
+ * distribution of the software without specific, written prior
+ * permission. The author makes no representations about the
+ * suitability of this software for any purpose.  It is provided "as
+ * is" without express or implied warranty.
+ *
+ * pool_ssl.c: ssl negotiation functions
+ *
+ */
+
+#include "config.h"
+#include "pool.h"
+
+#ifdef USE_SSL
+
+#include <arpa/inet.h> /* for htonl() */
+
+/* Major/minor codes to negotiate SSL prior to startup packet */
+#define NEGOTIATE_SSL_CODE ( 1234<<16 | 5679 )
+
+/* enum flag for differentiating server->client vs client->server SSL */
+enum ssl_conn_type { ssl_conn_clientserver, ssl_conn_serverclient };
+
+/* perform per-connection ssl initialization.  returns nonzero on error */
+static int init_ssl_ctx(POOL_CONNECTION *cp, enum ssl_conn_type conntype);
+
+/* attempt to negotiate a secure connection */
+void pool_ssl_negotiate_clientserver(POOL_CONNECTION *cp) {
+  int ssl_packet[2] = { htonl(sizeof(int)*2), htonl(NEGOTIATE_SSL_CODE) };
+  char server_response;
+
+  cp->ssl_active = -1;
+
+  if ( (!pool_config->ssl) || init_ssl_ctx(cp, ssl_conn_clientserver))
+    return;
+
+  pool_debug("pool_ssl: sending client->server SSL request");
+  pool_write_and_flush(cp, ssl_packet, sizeof(int)*2);
+  pool_read(cp, &server_response, 1);
+  pool_debug("pool_ssl: client->server SSL response: %c", server_response);
+
+  switch (server_response) {
+    case 'S':
+      SSL_set_fd(cp->ssl, cp->fd);
+      if (SSL_connect(cp->ssl) < 0) {
+        pool_error("pool_ssl: SSL_connect failed: %ld", ERR_get_error());
+      } else {
+        cp->ssl_active = 1;
+      }
+      break;
+    case 'N':
+      pool_error("pool_ssl: server doesn't want to talk SSL");
+      break;
+    default:
+      pool_error("pool_ssl: unhandled response: %c", server_response);
+      break;
+  }
+}
+
+
+/* attempt to negotiate a secure connection */
+void pool_ssl_negotiate_serverclient(POOL_CONNECTION *cp) {
+
+  cp->ssl_active = -1;
+
+  if ( (!pool_config->ssl) || init_ssl_ctx(cp, ssl_conn_serverclient)) {
+    /* write back an "SSL reject" response before returning */
+    pool_write_and_flush(cp, "N", 1);
+  } else {
+    /* write back an "SSL accept" response */
+    pool_write_and_flush(cp, "S", 1);
+
+    SSL_set_fd(cp->ssl, cp->fd);
+    if (SSL_accept(cp->ssl) < 0) {
+      pool_error("pool_ssl: SSL_accept failed: %ld", ERR_get_error());
+    } else {
+      cp->ssl_active = 1;
+    }
+  }
+}
+
+void pool_ssl_close(POOL_CONNECTION *cp) {
+  if (cp->ssl) { 
+    SSL_shutdown(cp->ssl); 
+    SSL_free(cp->ssl); 
+  } 
+
+  if (cp->ssl_ctx) 
+    SSL_CTX_free(cp->ssl_ctx);
+}
+
+int pool_ssl_read(POOL_CONNECTION *cp, void *buf, int size) {
+  return SSL_read(cp->ssl, buf, size);
+}
+
+int pool_ssl_write(POOL_CONNECTION *cp, const void *buf, int size) {
+  return SSL_write(cp->ssl, buf, size);
+}
+
+static int init_ssl_ctx(POOL_CONNECTION *cp, enum ssl_conn_type conntype) {
+  int error = 0;
+
+  /* initialize SSL members */
+  cp->ssl_ctx = SSL_CTX_new(TLSv1_method());
+  if (! cp->ssl_ctx) {
+    pool_error("pool_ssl: SSL_CTX_new failed: %ld", ERR_get_error());
+    error = -1;
+  }
+
+  if ( conntype == ssl_conn_serverclient) {
+    if ( (!error) && SSL_CTX_use_certificate_file(cp->ssl_ctx, 
+                                                  pool_config->ssl_cert,
+                                                  SSL_FILETYPE_PEM) <= 0) {
+      pool_error("pool_ssl: SSL cert failure: %ld", ERR_get_error());
+      error = -1;
+    }
+
+    if ( (!error) && SSL_CTX_use_PrivateKey_file(cp->ssl_ctx, 
+                                                 pool_config->ssl_key, 
+                                                 SSL_FILETYPE_PEM) <= 0) {
+      pool_error("pool_ssl: SSL key failure: %ld", ERR_get_error());
+      error = -1;
+    }
+  }
+
+  if (! error) {
+    cp->ssl = SSL_new(cp->ssl_ctx);
+    if (! cp->ssl) {
+      pool_error("pool_ssl: SSL_new failed: %ld", ERR_get_error());
+      error = -1;
+    }
+  }
+
+  return error;
+}
+
+#else /* USE_SSL: wrap / no-op ssl functionality if it's not available */
+
+void pool_ssl_negotiate_serverclient(POOL_CONNECTION *cp) {
+  pool_debug("pool_ssl: SSL requested but SSL support is not available");
+  pool_write_and_flush(cp, "N", 1);
+  cp->ssl_active = -1;
+}
+
+void pool_ssl_negotiate_clientserver(POOL_CONNECTION *cp) {
+  pool_debug("pool_ssl: SSL requested but SSL support is not available");
+  cp->ssl_active = -1;
+}
+
+void pool_ssl_close(POOL_CONNECTION *cp) { return; }
+
+int pool_ssl_read(POOL_CONNECTION *cp, void *buf, int size) {
+  pool_error("pool_ssl: SSL i/o called but SSL support is not available");
+  notice_backend_error(cp->db_node_id);
+  child_exit(1);
+  return -1; /* never reached */
+}
+
+int pool_ssl_write(POOL_CONNECTION *cp, const void *buf, int size) {
+  pool_error("pool_ssl: SSL i/o called but SSL support is not available");
+  notice_backend_error(cp->db_node_id);
+  child_exit(1);
+  return -1; /* never reached */
+}
+
+#endif /* USE_SSL */
diff --git a/pool_stream.c b/pool_stream.c
index 974e21c..d6e8d91 100644
--- a/pool_stream.c
+++ b/pool_stream.c
@@ -109,6 +109,9 @@ void pool_close(POOL_CONNECTION *cp)
 	if (cp->buf2)
 		free(cp->buf2);
 	pool_discard_params(&cp->params);
+
+	pool_ssl_close(cp);
+
 	free(cp);
 }
 
@@ -144,7 +147,12 @@ int pool_read(POOL_CONNECTION *cp, void *buf, int len)
 			}
 		}
 
-		readlen = read(cp->fd, readbuf, READBUFSZ);
+		if (cp->ssl_active > 0) {
+		  readlen = pool_ssl_read(cp, readbuf, READBUFSZ);
+		} else {
+		  readlen = read(cp->fd, readbuf, READBUFSZ);
+		}
+
 		if (readlen == -1)
 		{
 			if (errno == EINTR || errno == EAGAIN)
@@ -254,7 +262,12 @@ char *pool_read2(POOL_CONNECTION *cp, int len)
 			}
 		}
 
-		readlen = read(cp->fd, buf, len);
+		if (cp->ssl_active > 0) {
+		  readlen = pool_ssl_read(cp, buf, len);
+		} else {
+		  readlen = read(cp->fd, buf, len);
+		}
+
 		if (readlen == -1)
 		{
 			if (errno == EINTR || errno == EAGAIN)
@@ -404,7 +417,11 @@ int pool_flush_it(POOL_CONNECTION *cp)
 			}
 		}
 #endif
-		sts = write(cp->fd, cp->wbuf + offset, wlen);
+		if (cp->ssl_active > 0) {
+		  sts = pool_ssl_write(cp, cp->wbuf + offset, wlen);
+		} else {
+		  sts = write(cp->fd, cp->wbuf + offset, wlen);
+		}
 
 		if (sts > 0)
 		{
@@ -593,7 +610,12 @@ char *pool_read_string(POOL_CONNECTION *cp, int *len, int line)
 			}
 		}
 
-		readlen = read(cp->fd, cp->sbuf+readp, readsize);
+		if (cp->ssl_active > 0) {
+		  readlen = pool_ssl_read(cp, cp->sbuf+readp, readsize);
+		} else {
+		  readlen = read(cp->fd, cp->sbuf+readp, readsize);
+		}
+
 		if (readlen == -1)
 		{
 			pool_error("pool_read_string: read() failed. reason:%s", strerror(errno));
-- 
1.6.6



More information about the Pgpool-hackers mailing list