From 34faf166c6f37b90361bed54ddc908740c2c3cef Mon Sep 17 00:00:00 2001 From: Andrii Soldatenko Date: Tue, 28 Jun 2022 18:38:36 +0200 Subject: [PATCH 01/16] add gssapi authentication --- include/bouncer.h | 7 ++++++ include/pktbuf.h | 3 +++ src/proto.c | 7 ++++++ src/sbuf.c | 13 ++++++++++++ src/server.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 83 insertions(+), 1 deletion(-) diff --git a/include/bouncer.h b/include/bouncer.h index ea2555135b73..4ca29b17c5b2 100644 --- a/include/bouncer.h +++ b/include/bouncer.h @@ -81,6 +81,12 @@ enum SSLMode { SSLMODE_VERIFY_FULL }; +//enum ServerGSSEncMode { +// SERVER_GSSENCMODE_DISABLED, +// SERVER_GSSENCMODE_ENABLED, +// SERVER_GSSENCMODE_REQUIRE +//}; + #define is_server_socket(sk) ((sk)->state >= SV_FREE) @@ -409,6 +415,7 @@ struct PgSocket { bool wait_for_response:1;/* console client: waits for completion of PAUSE/SUSPEND cmd */ bool wait_sslchar:1; /* server: waiting for ssl response: S/N */ + bool wait_gssencchar:1; /* server: waiting for gss enc response: G/N */ int expect_rfq_count; /* client: count of ReadyForQuery packets client should see */ diff --git a/include/pktbuf.h b/include/pktbuf.h index fbe1fc5cd2b9..23255a333d1c 100644 --- a/include/pktbuf.h +++ b/include/pktbuf.h @@ -115,6 +115,9 @@ void pktbuf_write_ExtQuery(PktBuf *buf, const char *query, int nargs, ...); #define pktbuf_write_SSLRequest(buf) \ pktbuf_write_generic(buf, PKT_SSLREQ, "") +#define pktbuf_write_GSSEncRequest(buf) \ + pktbuf_write_generic(buf, PKT_GSSENCREQ, "") + /* * Shortcut for creating DataRow in memory. */ diff --git a/src/proto.c b/src/proto.c index 8eb8bdbf6751..5e8e77f76508 100644 --- a/src/proto.c +++ b/src/proto.c @@ -576,6 +576,13 @@ bool send_sslreq_packet(PgSocket *server) return res; } +bool send_gssencreq_packet(PgSocket *server) +{ + int res; + SEND_wrap(16, pktbuf_write_GSSEncRequest, res, server); + return res; +} + /* * decode DataRow packet (opposite of pktbuf_write_DataRow) * diff --git a/src/sbuf.c b/src/sbuf.c index 46f8adaf11fb..bb60cab71c16 100644 --- a/src/sbuf.c +++ b/src/sbuf.c @@ -104,6 +104,19 @@ static const SBufIO tls_sbufio_ops = { static void sbuf_tls_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf); #endif +/* I/O over GSS Enc */ +//#ifdef USE_TLS +static ssize_t gssenc_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len); +static ssize_t gssenc_sbufio_send(struct SBuf *sbuf, const void *data, size_t len); +static int gssenc_sbufio_close(struct SBuf *sbuf); +static const SBufIO gssenc_sbufio_ops = { + gssenc_sbufio_recv, + gssenc_sbufio_send, + gssenc_sbufio_close +}; +static void sbuf_gssenc_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf); +//#endif + /********************************* * Public functions *********************************/ diff --git a/src/server.c b/src/server.c index d0c24e194f55..a5e85402aa80 100644 --- a/src/server.c +++ b/src/server.c @@ -462,6 +462,11 @@ static bool handle_connect(PgSocket *server) res = send_sslreq_packet(server); if (res) server->wait_sslchar = true; + } else if (!is_unix) { // TODO: make it work like SSLMODE_ENABLED above + slog_noise(server, "P: GSSEnc request"); + res = send_gssencreq_packet(server); + if (res) + server->wait_gssencchar = true; } else { slog_noise(server, "P: startup"); res = send_startup_packet(server); @@ -497,7 +502,7 @@ static bool handle_sslchar(PgSocket *server, struct MBuf *data) if (schar == 'S') { slog_noise(server, "launching tls"); - ok = sbuf_tls_connect(&server->sbuf, server->pool->db->host); + ok = sbuf_gssenc_connect(&server->sbuf, server->pool->db->host); } else if (server_connect_sslmode >= SSLMODE_REQUIRE) { disconnect_server(server, false, "server refused SSL"); return false; @@ -514,6 +519,49 @@ static bool handle_sslchar(PgSocket *server, struct MBuf *data) return ok; } +static bool handle_gssencchar(PgSocket *server, struct MBuf *data) +{ + uint8_t gchar = '?'; + bool ok; + + server->wait_gssencchar = false; + + ok = mbuf_get_byte(data, &gchar); + if (!ok || (gchar != 'G' && gchar != 'N')) { + disconnect_server(server, false, "bad gssencreq answer"); + return false; + } + /* + * At this point we should have no data already buffered. If + * we do, it was received before we performed the SSL + * handshake, so it wasn't encrypted and indeed may have been + * injected by a man-in-the-middle. + */ + if (mbuf_avail_for_read(data) != 0) { + disconnect_server(server, false, "received unencrypted data after GSSEnc response"); + return false; + } + + if (gchar == 'G') { + slog_noise(server, "launching gssenc"); + ok = sbuf_tls_connect(&server->sbuf, server->pool->db->host); +// TODO: allow refusal +// } else if (server_connect_sslmode >= GSSENCMODE_REQUIRE) { +// disconnect_server(server, false, "server refused GSSEnc"); +// return false; + } else { + /* proceed with non-TLS connection */ + ok = send_startup_packet(server); + } + + if (ok) { + sbuf_prepare_skip(&server->sbuf, 1); + } else { + disconnect_server(server, false, "gssencreq processing failed"); + } + return ok; +} + /* callback from SBuf */ bool server_proto(SBuf *sbuf, SBufEvent evtype, struct MBuf *data) { @@ -542,6 +590,10 @@ bool server_proto(SBuf *sbuf, SBufEvent evtype, struct MBuf *data) res = handle_sslchar(server, data); break; } + if (server->wait_gssencchar) { + res = handle_gssencchar(server, data); + break; + } if (incomplete_header(data)) { slog_noise(server, "S: got partial header, trying to wait a bit"); break; From 01702a7179e0b35a76c5fa204af6167e75ac997a Mon Sep 17 00:00:00 2001 From: Andrii Soldatenko Date: Wed, 29 Jun 2022 22:02:32 +0200 Subject: [PATCH 02/16] more implementation of gssenc --- configure.ac | 17 ++++ include/bouncer.h | 18 +++- include/proto.h | 1 + include/sbuf.h | 5 +- src/main.c | 11 +++ src/sbuf.c | 232 +++++++++++++++++++++++++++++++++++++++++++++- src/server.c | 2 +- 7 files changed, 276 insertions(+), 10 deletions(-) diff --git a/configure.ac b/configure.ac index 639609b0cf48..24e08e91aef7 100644 --- a/configure.ac +++ b/configure.ac @@ -93,6 +93,22 @@ if test "$with_systemd" = yes; then AC_SEARCH_LIBS(sd_notify, systemd) fi +dnl Check for server GSSAPI Encryption support +server_gssenc_support=no +AC_ARG_WITH(server-gssenc, + AC_HELP_STRING([--with-server-gssenc], [build with server GSSAPI Encryption support]), + [ GSS= + if test "$withval" != no; then + # Look for GSSAPI header and lib + AC_CHECK_HEADERS(gssapi.h, [have_gss_header=y]) + AC_SEARCH_LIBS(gss_accept_sec_context, [gssapi_krb5 gssapi], [have_libgss=t]) + if test x"${have_gss_header}" != x -a x"${have_libgss}" != x; then + server_gssenc_support=yes + AC_DEFINE(HAVE_SERVER_GSSENC, 1, [Server GSSAPI Encryption support]) + fi + fi + ], []) + ## ## DNS backend ## @@ -236,4 +252,5 @@ fi echo " pam = $pam_support" echo " systemd = $with_systemd" echo " tls = $tls_support" +echo " servergssenc = $server_gssenc_support" echo "" diff --git a/include/bouncer.h b/include/bouncer.h index 4ca29b17c5b2..a31ed4c167da 100644 --- a/include/bouncer.h +++ b/include/bouncer.h @@ -42,6 +42,13 @@ #define sd_notifyf(ue, f, ...) #endif +#ifdef HAVE_GSSAPI_H +#include +#include +#include +#endif + + /* global libevent handle */ extern struct event_base *pgb_event_base; @@ -81,11 +88,12 @@ enum SSLMode { SSLMODE_VERIFY_FULL }; -//enum ServerGSSEncMode { -// SERVER_GSSENCMODE_DISABLED, -// SERVER_GSSENCMODE_ENABLED, -// SERVER_GSSENCMODE_REQUIRE -//}; +enum ServerGSSEncMode { + SERVER_GSSENCMODE_DISABLED, + SERVER_GSSENCMODE_ALLOW, + SERVER_GSSENCMODE_PREFER, + SERVER_GSSENCMODE_REQUIRE +}; #define is_server_socket(sk) ((sk)->state >= SV_FREE) diff --git a/include/proto.h b/include/proto.h index 13f5fd765905..bd0bfe4e9495 100644 --- a/include/proto.h +++ b/include/proto.h @@ -50,6 +50,7 @@ bool welcome_client(PgSocket *client) _MUSTCHECK; bool answer_authreq(PgSocket *server, PktHdr *pkt) _MUSTCHECK; bool send_startup_packet(PgSocket *server) _MUSTCHECK; +bool send_gssencreq_packet(PgSocket *server) _MUSTCHECK; bool send_sslreq_packet(PgSocket *server) _MUSTCHECK; int scan_text_result(struct MBuf *pkt, const char *tupdesc, ...) _MUSTCHECK; diff --git a/include/sbuf.h b/include/sbuf.h index ddc3f7ccaef4..567e75a8547c 100644 --- a/include/sbuf.h +++ b/include/sbuf.h @@ -73,6 +73,7 @@ struct SBuf { uint8_t wait_type; /* track wait state */ uint8_t pkt_action; /* method for handling current pkt */ uint8_t tls_state; /* progress of tls */ + uint8_t gss_state; /* progress of gss */ int sock; /* fd for this socket */ @@ -84,8 +85,9 @@ struct SBuf { IOBuf *io; /* data buffer, lazily allocated */ - const SBufIO *ops; /* normal vs. TLS */ + const SBufIO *ops; /* normal vs. TLS vs. GSS */ struct tls *tls; /* TLS context */ + struct gss_ctx_id_struct *gss; const char *tls_host; /* target hostname */ }; @@ -110,6 +112,7 @@ extern int client_accept_sslmode; */ extern int server_connect_sslmode; +bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) _MUSTCHECK; bool sbuf_tls_setup(void); bool sbuf_tls_accept(SBuf *sbuf) _MUSTCHECK; bool sbuf_tls_connect(SBuf *sbuf, const char *hostname) _MUSTCHECK; diff --git a/src/main.c b/src/main.c index fb76a56c8264..8edd41eba420 100644 --- a/src/main.c +++ b/src/main.c @@ -177,6 +177,8 @@ char *cf_client_tls_ciphers; char *cf_client_tls_dheparams; char *cf_client_tls_ecdhecurve; +int cf_server_gssencmode; + int cf_server_tls_sslmode; char *cf_server_tls_protocols; char *cf_server_tls_ca_file; @@ -222,6 +224,14 @@ const struct CfLookup sslmode_map[] = { { NULL } }; +const struct CfLookup gssencmode_map[] = { + { "disable", SERVER_GSSENCMODE_DISABLED }, + { "allow", SERVER_GSSENCMODE_ALLOW }, + { "prefer", SERVER_GSSENCMODE_PREFER }, + { "require", SERVER_GSSENCMODE_REQUIRE }, + { NULL } +}; + /* * Add new parameters in alphabetical order. This order is used by SHOW CONFIG. */ @@ -280,6 +290,7 @@ CF_ABS("server_check_query", CF_STR, cf_server_check_query, 0, "select 1"), CF_ABS("server_connect_timeout", CF_TIME_USEC, cf_server_connect_timeout, 0, "15"), CF_ABS("server_fast_close", CF_INT, cf_server_fast_close, 0, "0"), CF_ABS("server_idle_timeout", CF_TIME_USEC, cf_server_idle_timeout, 0, "600"), +CF_ABS("server_gssencmode", CF_LOOKUP(gssencmode_map), cf_server_gssencmode, 0, "disable"), CF_ABS("server_lifetime", CF_TIME_USEC, cf_server_lifetime, 0, "3600"), CF_ABS("server_login_retry", CF_TIME_USEC, cf_server_login_retry, 0, "15"), CF_ABS("server_reset_query", CF_STR, cf_server_reset_query, 0, "DISCARD ALL"), diff --git a/src/sbuf.c b/src/sbuf.c index bb60cab71c16..e6100fafddbb 100644 --- a/src/sbuf.c +++ b/src/sbuf.c @@ -49,6 +49,15 @@ enum TLSState { SBUF_TLS_OK, }; +#ifdef HAVE_SERVER_GSSENC +enum GSSEncState { + SBUF_GSSENC_NONE, + SBUF_GSSENC_INITIAL, + SBUF_GSSENC_CONTINUE, + SBUF_GSSENC_OK, +}; +#endif + enum WaitType { W_NONE = 0, W_CONNECT, @@ -105,7 +114,7 @@ static void sbuf_tls_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf); #endif /* I/O over GSS Enc */ -//#ifdef USE_TLS +#ifdef HAVE_SERVER_GSSENC static ssize_t gssenc_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len); static ssize_t gssenc_sbufio_send(struct SBuf *sbuf, const void *data, size_t len); static int gssenc_sbufio_close(struct SBuf *sbuf); @@ -114,8 +123,8 @@ static const SBufIO gssenc_sbufio_ops = { gssenc_sbufio_send, gssenc_sbufio_close }; -static void sbuf_gssenc_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf); -//#endif +//static void sbuf_gssenc_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf); +#endif /********************************* * Public functions @@ -1284,3 +1293,220 @@ static bool handle_tls_handshake(SBuf *sbuf) } #endif + +/* + * Server GSS Encryption support. + */ + +#ifdef HAVE_SERVER_GSSENC +static int gssenc_sbufio_close(struct SBuf *sbuf) +{ + log_noise("gss_close"); + if (sbuf->gss) { +// TODO: free gss memory +// tls_close(sbuf->tls); +// tls_free(sbuf->tls); + sbuf->gss = NULL; + } + if (sbuf->sock > 0) { + safe_close(sbuf->sock); + sbuf->sock = 0; + } + return 0; +} + +static ssize_t gssenc_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len) +{ + return 1; +} +static ssize_t gssenc_sbufio_send(struct SBuf *sbuf, const void *data, size_t len) +{ + return 1; +} + +static void +release_buffer(gss_buffer_t buf) +{ + free(buf->value); + buf->value = NULL; + buf->length = 0; +} + +/* + * Helper to send a token on the specified file descriptor. + * + * If errors are encountered, this routine must not directly cause + * termination of the process because compliant GSS applications + * must release resources allocated by the GSS library before + * exiting. + * + * Returns 0 on success, nonzero on failure. + */ +static int +send_token(int fd, gss_buffer_t token) +{ + /* + * Supply token framing and transmission code here. + * + * It is advisable for the application protocol to specify the + * length of the token being transmitted unless the underlying + * transit does so implicitly. + * + * In addition to checking for error returns from whichever + * syscall(s) are used to send data, applications should have + * a loop to handle EINTR returns. + */ + return 1; +} + +/* + * Helper to receive a token on the specified file descriptor. + * + * If errors are encountered, this routine must not directly cause + * termination of the process because compliant GSS applications + * must release resources allocated by the GSS library before + * exiting. + * + * Returns 0 on success, nonzero on failure. + */ +static int +receive_token(int fd, gss_buffer_t token) +{ + /* + * Supply token framing and transmission code here. + * + * In addition to checking for error returns from whichever + * syscall(s) are used to receive data, applications should have + * a loop to handle EINTR returns. + * + * This routine is assumed to allocate memory for the local copy + * of the received token, which must be freed with release_buffer(). + */ + return 1; +} + +/* + * Connect to remote GSS Enc host. + */ + +bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) +{ +// int err; + int initiator_established = 0, ret; + gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; + OM_uint32 major, minor, req_flags, ret_flags; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc name_buf = GSS_C_EMPTY_BUFFER; + gss_name_t target_name = GSS_C_NO_NAME; + + /* Applications should set target_name to a real value. */ + name_buf.value = "postgres/kerberized-postgres@EXAMPLE.COM"; + name_buf.length = strlen(name_buf.value); + major = gss_import_name(&minor, &name_buf, + GSS_KRB5_NT_PRINCIPAL_NAME, &target_name); + if (GSS_ERROR(major)) { + log_noise("Could not import name\n"); + return false; + } + + if (!sbuf_pause(sbuf)) + return false; + +// if (cf_server_tls_sslmode != SSLMODE_VERIFY_FULL) +// hostname = NULL; + +// ctls = tls_client(); +// if (!ctls) +// return false; +// err = tls_configure(ctls, server_connect_conf); +// if (err < 0) { +// log_error("tls client config failed: %s", tls_error(ctls)); +// tls_free(ctls); +// return false; +// } + + /* Mutual authentication will require a token from acceptor to + * initiator and thus a second call to gss_init_sec_context(). */ + req_flags = GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG; + + while (!initiator_established) { + /* The initiator_cred_handle, mech_type, time_req, + * input_chan_bindings, actual_mech_type, and time_rec + * parameters are not needed in many cases. We pass + * GSS_C_NO_CREDENTIAL, GSS_C_NO_OID, 0, NULL, NULL, and NULL + * for them, respectively. */ + major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, &ctx, + target_name, GSS_C_NO_OID, + req_flags, 0, NULL, &input_token, + NULL, &output_token, &ret_flags, + NULL); + /* This was allocated by receive_token() and is no longer + * needed. Free it now to avoid leaks if the loop continues. */ + release_buffer(&input_token); + + /* Always send a token if we are expecting another input token + * (GSS_S_CONTINUE_NEEDED is set) or if it is nonempty. */ + if ((major & GSS_S_CONTINUE_NEEDED) || + output_token.length > 0) { + ret = send_token(sbuf->sock, &output_token); + if (ret != 0) + goto cleanup; + } + /* Check for errors after sending the token so that we will send + * error tokens. */ + if (GSS_ERROR(major)) { + log_warning("gss_init_sec_context() error major 0x%x\n", major); + goto cleanup; + } + /* Free the output token's storage; we don't need it anymore. + * gss_release_buffer() is safe to call on the output buffer + * from gss_int_sec_context(), even if there is no storage + * associated with that buffer. */ + (void)gss_release_buffer(&minor, &output_token); + + if (major & GSS_S_CONTINUE_NEEDED) { + ret = receive_token(sbuf->sock, &input_token); + if (ret != 0) + goto cleanup; + } else if (major == GSS_S_COMPLETE) { + initiator_established = 1; + } else { + /* This situation is forbidden by RFC 2743. Bail out. */ + log_warning("major not complete or continue but not error\n"); + goto cleanup; + } + } /* while (!initiator_established) */ + if ((ret_flags & req_flags) != req_flags) { + log_warning("Negotiated context does not support requested flags\n"); + goto cleanup; + } + log_noise("Initiator's context negotiation successful\n"); + + sbuf->gss = ctx; +// sbuf->tls = ctls; +// sbuf->tls_host = hostname; + sbuf->ops = &gssenc_sbufio_ops; +// sbuf->ops = &tls_sbufio_ops; + +// err = tls_connect_fds(sbuf->tls, sbuf->sock, sbuf->sock, sbuf->tls_host); +// if (err < 0) { +// log_warning("GSS connect error: %s", tls_error(sbuf->tls)); +// return false; +// } + + sbuf->gss_state = SBUF_GSSENC_INITIAL; + return true; + +cleanup: + /* We are required to release storage for nonzero-length output + * tokens. gss_release_buffer() zeros the length, so we + * will not attempt to release the same buffer twice. */ + if (output_token.length > 0) + (void)gss_release_buffer(&minor, &output_token); + /* Do not request a context deletion token; pass NULL. */ + (void)gss_delete_sec_context(&minor, &ctx, NULL); + (void)gss_release_name(&minor, &target_name); + return true; +} +#endif diff --git a/src/server.c b/src/server.c index a5e85402aa80..0d89008b0ecc 100644 --- a/src/server.c +++ b/src/server.c @@ -544,7 +544,7 @@ static bool handle_gssencchar(PgSocket *server, struct MBuf *data) if (gchar == 'G') { slog_noise(server, "launching gssenc"); - ok = sbuf_tls_connect(&server->sbuf, server->pool->db->host); + ok = sbuf_gssenc_connect(&server->sbuf, server->pool->db->host); // TODO: allow refusal // } else if (server_connect_sslmode >= GSSENCMODE_REQUIRE) { // disconnect_server(server, false, "server refused GSSEnc"); From 8f680021f191853067111d607053d29fd85f73f1 Mon Sep 17 00:00:00 2001 From: coryastronomer <107139210+coryastronomer@users.noreply.github.com> Date: Fri, 1 Jul 2022 00:49:06 -0500 Subject: [PATCH 03/16] experimental implementations of send_token and receive_token --- src/sbuf.c | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/sbuf.c b/src/sbuf.c index e6100fafddbb..a0c6f180ed9a 100644 --- a/src/sbuf.c +++ b/src/sbuf.c @@ -123,6 +123,8 @@ static const SBufIO gssenc_sbufio_ops = { gssenc_sbufio_send, gssenc_sbufio_close }; +static int receive_token(int fd, gss_buffer_t token); +static int send_token(int fd, gss_buffer_t token); //static void sbuf_gssenc_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf); #endif @@ -1317,11 +1319,23 @@ static int gssenc_sbufio_close(struct SBuf *sbuf) static ssize_t gssenc_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len) { - return 1; + char tmp[16388]; + receive_token(sbuf->sock, &tmp); + int maj = 0; + int min = 0; + maj = gss_unwrap(&min, sbuf->gss, &tmp, &dst, NULL, NULL); + log_warning("gssenc_sbufio_recv"); + return 0; } static ssize_t gssenc_sbufio_send(struct SBuf *sbuf, const void *data, size_t len) { - return 1; + char tmp[16388]; + int maj = 0; + int min = 0; + maj = gss_wrap(&min, sbuf->gss, 1, 0, &data, NULL, &tmp); + send_token(sbuf->sock, tmp); + log_warning("gssenc_sbufio_send"); + return 0; } static void @@ -1356,7 +1370,12 @@ send_token(int fd, gss_buffer_t token) * syscall(s) are used to send data, applications should have * a loop to handle EINTR returns. */ - return 1; + uint32_t tmp; + tmp = token->length; + send(fd, &tmp, sizeof(uint32_t), MSG_MORE); + send(fd, &token, tmp, 0); + fprintf(stderr, "send_token"); + return 0; } /* @@ -1382,7 +1401,21 @@ receive_token(int fd, gss_buffer_t token) * This routine is assumed to allocate memory for the local copy * of the received token, which must be freed with release_buffer(). */ - return 1; + char tmp[4]; + int len; + uint32_t total; + + len = recv(fd, &tmp, sizeof(uint32_t), MSG_WAITALL); + total = ntohl(tmp); + + if (total > 16384) { + return EMSGSIZE; + } + + len = recv(fd, &tmp, total, MSG_WAITALL); + + fprintf(stderr, "receive_token"); + return 0; } /* From a5a739afc7f30deca207c079f0d808181428ec53 Mon Sep 17 00:00:00 2001 From: coryastronomer <107139210+coryastronomer@users.noreply.github.com> Date: Wed, 6 Jul 2022 10:13:53 -0500 Subject: [PATCH 04/16] updated code from gss-client.c --- src/sbuf.c | 275 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 210 insertions(+), 65 deletions(-) diff --git a/src/sbuf.c b/src/sbuf.c index a0c6f180ed9a..4ab8958716d3 100644 --- a/src/sbuf.c +++ b/src/sbuf.c @@ -123,8 +123,8 @@ static const SBufIO gssenc_sbufio_ops = { gssenc_sbufio_send, gssenc_sbufio_close }; -static int receive_token(int fd, gss_buffer_t token); -static int send_token(int fd, gss_buffer_t token); +static int recv_token(int s, int *flags, gss_buffer_t tok); +static int send_token(int s, int flags, gss_buffer_t tok); //static void sbuf_gssenc_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf); #endif @@ -1320,22 +1320,37 @@ static int gssenc_sbufio_close(struct SBuf *sbuf) static ssize_t gssenc_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len) { char tmp[16388]; - receive_token(sbuf->sock, &tmp); + int token_flags; + log_warning("gssenc_sbufio_recv start"); + recv_token(sbuf->sock, &token_flags, &tmp); + log_warning("gssenc_sbufio_recv token received"); int maj = 0; int min = 0; maj = gss_unwrap(&min, sbuf->gss, &tmp, &dst, NULL, NULL); - log_warning("gssenc_sbufio_recv"); + log_warning("gssenc_sbufio_recv end"); return 0; } static ssize_t gssenc_sbufio_send(struct SBuf *sbuf, const void *data, size_t len) { - char tmp[16388]; - int maj = 0; - int min = 0; - maj = gss_wrap(&min, sbuf->gss, 1, 0, &data, NULL, &tmp); - send_token(sbuf->sock, tmp); - log_warning("gssenc_sbufio_send"); - return 0; + log_warning("gssenc_sbufio_send start"); + char tmp[16384]; + gss_buffer_desc in_buf, out_buf = GSS_C_EMPTY_BUFFER; + in_buf.length = len; + in_buf.value = (char *) data; + out_buf.value = NULL; + out_buf.length = 0; + OM_uint32 maj = 0; + OM_uint32 min = 0; + int state; + ssize_t out; + maj = gss_wrap(&min, sbuf->gss, 1, GSS_C_QOP_DEFAULT, &in_buf, &state, &out_buf); + if (GSS_ERROR(maj)) { + log_warning("gssenc_sbufio_send - gss_wrap() error major 0x%x minor 0x%x\n", maj, min); + return -1; + } + out = send_token(sbuf->sock, NULL, &out_buf); + log_warning("gssenc_sbufio_send end"); + return len; } static void @@ -1346,75 +1361,204 @@ release_buffer(gss_buffer_t buf) buf->length = 0; } +static int +write_all(int fildes, const void *data, unsigned int nbyte) +{ + int ret; + const char *ptr, *buf = data; + + for (ptr = buf; nbyte; ptr += ret, nbyte -= ret) { + ret = send(fildes, ptr, nbyte, 0); + if (ret < 0) { + if (errno == EINTR) + continue; + return (ret); + } else if (ret == 0) { + return (ptr - buf); + } + } + + return (ptr - buf); +} + /* - * Helper to send a token on the specified file descriptor. + * Function: send_token + * + * Purpose: Writes a token to a file descriptor. + * + * Arguments: + * + * s (r) an open file descriptor + * flags (r) the flags to write + * tok (r) the token to write + * + * Returns: 0 on success, -1 on failure * - * If errors are encountered, this routine must not directly cause - * termination of the process because compliant GSS applications - * must release resources allocated by the GSS library before - * exiting. + * Effects: * - * Returns 0 on success, nonzero on failure. + * If the flags are non-null, send_token writes the token flags (a + * single byte, even though they're passed in in an integer). Next, + * the token length (as a network long) and then the token data are + * written to the file descriptor s. It returns 0 on success, and -1 + * if an error occurs or if it could not write all the data. */ + static int -send_token(int fd, gss_buffer_t token) +send_token(s, flags, tok) + int s; + int flags; + gss_buffer_t tok; { - /* - * Supply token framing and transmission code here. - * - * It is advisable for the application protocol to specify the - * length of the token being transmitted unless the underlying - * transit does so implicitly. - * - * In addition to checking for error returns from whichever - * syscall(s) are used to send data, applications should have - * a loop to handle EINTR returns. - */ - uint32_t tmp; - tmp = token->length; - send(fd, &tmp, sizeof(uint32_t), MSG_MORE); - send(fd, &token, tmp, 0); - fprintf(stderr, "send_token"); + int ret; + unsigned char char_flags = (unsigned char) flags; + unsigned char lenbuf[4]; + + if (char_flags) { + ret = write_all(s, (char *) &char_flags, 1); + if (ret != 1) { + perror("sending token flags"); + return -1; + } + } + if (tok->length > 0xffffffffUL) + abort(); + lenbuf[0] = (tok->length >> 24) & 0xff; + lenbuf[1] = (tok->length >> 16) & 0xff; + lenbuf[2] = (tok->length >> 8) & 0xff; + lenbuf[3] = tok->length & 0xff; + + ret = write_all(s, lenbuf, 4); + if (ret < 0) { + perror("sending token length"); + return -1; + } else if (ret != 4) { + return -1; + } + + ret = write_all(s, tok->value, tok->length); + if (ret < 0) { + perror("sending token data"); + return -1; + } else if ((size_t)ret != tok->length) { + return -1; + } + return 0; } +static int +read_all(int fildes, void *data, unsigned int nbyte) +{ + int ret; + char *ptr, *buf = data; + fd_set rfds; + struct timeval tv; + + FD_ZERO(&rfds); + FD_SET(fildes, &rfds); + tv.tv_sec = 300; + tv.tv_usec = 0; + + for (ptr = buf; nbyte; ptr += ret, nbyte -= ret) { + if (select(FD_SETSIZE, &rfds, NULL, NULL, &tv) <= 0 + || !FD_ISSET(fildes, &rfds)) + return (ptr - buf); + ret = recv(fildes, ptr, nbyte, 0); + if (ret < 0) { + if (errno == EINTR) + continue; + return (ret); + } else if (ret == 0) { + return (ptr - buf); + } + } + + return (ptr - buf); +} + /* - * Helper to receive a token on the specified file descriptor. + * Function: recv_token + * + * Purpose: Reads a token from a file descriptor. * - * If errors are encountered, this routine must not directly cause - * termination of the process because compliant GSS applications - * must release resources allocated by the GSS library before - * exiting. + * Arguments: * - * Returns 0 on success, nonzero on failure. + * s (r) an open file descriptor + * flags (w) the read flags + * tok (w) the read token + * + * Returns: 0 on success, -1 on failure + * + * Effects: + * + * recv_token reads the token flags (a single byte, even though + * they're stored into an integer, then reads the token length (as a + * network long), allocates memory to hold the data, and then reads + * the token data from the file descriptor s. It blocks to read the + * length and data, if necessary. On a successful return, the token + * should be freed with gss_release_buffer. It returns 0 on success, + * and -1 if an error occurs or if it could not read all the data. */ static int -receive_token(int fd, gss_buffer_t token) +recv_token(s, flags, tok) + int s; + int *flags; + gss_buffer_t tok; { - /* - * Supply token framing and transmission code here. - * - * In addition to checking for error returns from whichever - * syscall(s) are used to receive data, applications should have - * a loop to handle EINTR returns. - * - * This routine is assumed to allocate memory for the local copy - * of the received token, which must be freed with release_buffer(). - */ - char tmp[4]; - int len; - uint32_t total; - - len = recv(fd, &tmp, sizeof(uint32_t), MSG_WAITALL); - total = ntohl(tmp); - - if (total > 16384) { - return EMSGSIZE; + int ret; + unsigned char char_flags; + unsigned char lenbuf[4]; + + ret = read_all(s, (char *) &char_flags, 1); + if (ret < 0) { + perror("reading token flags"); + return -1; + } else if (!ret) { + return -1; + } else { + *flags = (int) char_flags; + } + + if (char_flags == 0) { + lenbuf[0] = 0; + ret = read_all(s, &lenbuf[1], 3); + if (ret < 0) { + perror("reading token length"); + return -1; + } else if (ret != 3) { + return -1; + } + } else { + ret = read_all(s, lenbuf, 4); + if (ret < 0) { + perror("reading token length"); + return -1; + } else if (ret != 4) { + return -1; + } } - len = recv(fd, &tmp, total, MSG_WAITALL); + tok->length = ((lenbuf[0] << 24) + | (lenbuf[1] << 16) + | (lenbuf[2] << 8) + | lenbuf[3]); + tok->value = (char *) malloc(tok->length ? tok->length : 1); + if (tok->length && tok->value == NULL) { + return -1; + } + + ret = read_all(s, (char *) tok->value, tok->length); + if (ret < 0) { + perror("reading token data"); + free(tok->value); + return -1; + } else if ((size_t)ret != tok->length) { + fprintf(stderr, "sending token data: %d of %d bytes written\n", + ret, (int) tok->length); + free(tok->value); + return -1; + } - fprintf(stderr, "receive_token"); return 0; } @@ -1432,6 +1576,7 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; gss_buffer_desc name_buf = GSS_C_EMPTY_BUFFER; gss_name_t target_name = GSS_C_NO_NAME; + int token_flags; /* Applications should set target_name to a real value. */ name_buf.value = "postgres/kerberized-postgres@EXAMPLE.COM"; @@ -1474,7 +1619,7 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) req_flags, 0, NULL, &input_token, NULL, &output_token, &ret_flags, NULL); - /* This was allocated by receive_token() and is no longer + /* This was allocated by recv_token() and is no longer * needed. Free it now to avoid leaks if the loop continues. */ release_buffer(&input_token); @@ -1482,7 +1627,7 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) * (GSS_S_CONTINUE_NEEDED is set) or if it is nonempty. */ if ((major & GSS_S_CONTINUE_NEEDED) || output_token.length > 0) { - ret = send_token(sbuf->sock, &output_token); + ret = send_token(sbuf->sock, NULL, &output_token); if (ret != 0) goto cleanup; } @@ -1499,7 +1644,7 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) (void)gss_release_buffer(&minor, &output_token); if (major & GSS_S_CONTINUE_NEEDED) { - ret = receive_token(sbuf->sock, &input_token); + ret = recv_token(sbuf->sock, &token_flags, &input_token); if (ret != 0) goto cleanup; } else if (major == GSS_S_COMPLETE) { From 2c26c29957dfbf8a20f4d8e6505cd9c034f329d8 Mon Sep 17 00:00:00 2001 From: coryastronomer <107139210+coryastronomer@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:07:40 -0500 Subject: [PATCH 05/16] correct handle_sslchar mistake --- src/server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.c b/src/server.c index 0d89008b0ecc..1d661ebaa1ee 100644 --- a/src/server.c +++ b/src/server.c @@ -502,7 +502,7 @@ static bool handle_sslchar(PgSocket *server, struct MBuf *data) if (schar == 'S') { slog_noise(server, "launching tls"); - ok = sbuf_gssenc_connect(&server->sbuf, server->pool->db->host); + ok = sbuf_tls_connect(&server->sbuf, server->pool->db->host); } else if (server_connect_sslmode >= SSLMODE_REQUIRE) { disconnect_server(server, false, "server refused SSL"); return false; From fc46b3ef271ec3c9d7618be3b0e011af021fb46b Mon Sep 17 00:00:00 2001 From: coryastronomer <107139210+coryastronomer@users.noreply.github.com> Date: Wed, 6 Jul 2022 18:28:02 -0500 Subject: [PATCH 06/16] added gssenc handshake code --- include/sbuf.h | 3 ++- src/client.c | 1 + src/sbuf.c | 44 ++++++++++++++++++++++++++++++++++++++++---- src/server.c | 17 +++++++++++++++++ 4 files changed, 60 insertions(+), 5 deletions(-) diff --git a/include/sbuf.h b/include/sbuf.h index 567e75a8547c..933fa45d462a 100644 --- a/include/sbuf.h +++ b/include/sbuf.h @@ -29,6 +29,7 @@ typedef enum { SBUF_EV_CONNECT_OK, /* got connection */ SBUF_EV_FLUSH, /* data is sent, buffer empty */ SBUF_EV_PKT_CALLBACK, /* next part of pkt data */ + SBUF_EV_GSSENC_READY, /* GSSENC was established */ SBUF_EV_TLS_READY /* TLS was established */ } SBufEvent; @@ -73,7 +74,7 @@ struct SBuf { uint8_t wait_type; /* track wait state */ uint8_t pkt_action; /* method for handling current pkt */ uint8_t tls_state; /* progress of tls */ - uint8_t gss_state; /* progress of gss */ + uint8_t gssenc_state; /* progress of gssenc */ int sock; /* fd for this socket */ diff --git a/src/client.c b/src/client.c index 8ffbbcdaac19..142c978dd566 100644 --- a/src/client.c +++ b/src/client.c @@ -985,6 +985,7 @@ bool client_proto(SBuf *sbuf, SBufEvent evtype, struct MBuf *data) switch (evtype) { case SBUF_EV_CONNECT_OK: case SBUF_EV_CONNECT_FAILED: + case SBUF_EV_GSSENC_READY: /* ^ those should not happen */ case SBUF_EV_RECV_FAILED: /* diff --git a/src/sbuf.c b/src/sbuf.c index 4ab8958716d3..e5e0aeb57e9e 100644 --- a/src/sbuf.c +++ b/src/sbuf.c @@ -52,8 +52,8 @@ enum TLSState { #ifdef HAVE_SERVER_GSSENC enum GSSEncState { SBUF_GSSENC_NONE, - SBUF_GSSENC_INITIAL, - SBUF_GSSENC_CONTINUE, + SBUF_GSSENC_DO_HANDSHAKE, + SBUF_GSSENC_IN_HANDSHAKE, SBUF_GSSENC_OK, }; #endif @@ -89,6 +89,7 @@ static bool sbuf_call_proto(SBuf *sbuf, int event) /* _MUSTCHECK */; static bool sbuf_actual_recv(SBuf *sbuf, size_t len) _MUSTCHECK; static bool sbuf_after_connect_check(SBuf *sbuf) _MUSTCHECK; static bool handle_tls_handshake(SBuf *sbuf) /* _MUSTCHECK */; +static bool handle_gssenc_handshake(SBuf *sbuf) /* _MUSTCHECK */; /* regular I/O */ static ssize_t raw_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len); @@ -125,7 +126,7 @@ static const SBufIO gssenc_sbufio_ops = { }; static int recv_token(int s, int *flags, gss_buffer_t tok); static int send_token(int s, int flags, gss_buffer_t tok); -//static void sbuf_gssenc_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf); +static void sbuf_gssenc_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf); #endif /********************************* @@ -802,6 +803,10 @@ static void sbuf_main_loop(SBuf *sbuf, bool skip_recv) sbuf->pkt_action = SBUF_TLS_IN_HANDSHAKE; handle_tls_handshake(sbuf); } + if (sbuf->gssenc_state == SBUF_GSSENC_DO_HANDSHAKE) { + sbuf->pkt_action = SBUF_GSSENC_IN_HANDSHAKE; + handle_gssenc_handshake(sbuf); + } } /* check if there is any error pending on socket */ @@ -1673,7 +1678,7 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) // return false; // } - sbuf->gss_state = SBUF_GSSENC_INITIAL; + sbuf->gssenc_state = SBUF_GSSENC_DO_HANDSHAKE; return true; cleanup: @@ -1687,4 +1692,35 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) (void)gss_release_name(&minor, &target_name); return true; } + +static bool handle_gssenc_handshake(SBuf *sbuf) +{ + sbuf->gssenc_state = SBUF_GSSENC_OK; + sbuf_call_proto(sbuf, SBUF_EV_GSSENC_READY); + return true; +} + +static void sbuf_gssenc_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf) +{ + SBuf *sbuf = _sbuf; + sbuf->wait_type = W_NONE; + if (!handle_gssenc_handshake(sbuf)) + sbuf_call_proto(sbuf, SBUF_EV_RECV_FAILED); +} +#else +//int client_accept_sslmode = SSLMODE_DISABLED; +//int server_connect_sslmode = SSLMODE_DISABLED; + +//bool sbuf_tls_setup(void) { return true; } +//bool sbuf_tls_accept(SBuf *sbuf) { return false; } +bool sbuf_gssenc__connect(SBuf *sbuf, const char *hostname) { return false; } + +void sbuf_cleanup(void) +{ +} + +static bool handle_gssenc_handshake(SBuf *sbuf) +{ + return false; +} #endif diff --git a/src/server.c b/src/server.c index 1d661ebaa1ee..09de9fd8aedf 100644 --- a/src/server.c +++ b/src/server.c @@ -686,6 +686,23 @@ bool server_proto(SBuf *sbuf, SBufEvent evtype, struct MBuf *data) else disconnect_server(server, false, "TLS startup failed"); break; + case SBUF_EV_GSSENC_READY: + Assert(server->state == SV_LOGIN); + +// tls_get_connection_info(server->sbuf.tls, infobuf, sizeof infobuf); +// if (cf_log_connections) { +// slog_info(server, "SSL established: %s", infobuf); +// } else { +// slog_noise(server, "SSL established: %s", infobuf); +// } + + server->request_time = get_cached_time(); + res = send_startup_packet(server); + if (res) + sbuf_continue(&server->sbuf); + else + disconnect_server(server, false, "GSSENC startup failed"); + break; } if (!res && pool->db->admin) takeover_login_failed(); From 646b515fcf292c2b4f2e38011a9ceb03042d2aec Mon Sep 17 00:00:00 2001 From: coryastronomer <107139210+coryastronomer@users.noreply.github.com> Date: Thu, 7 Jul 2022 18:38:02 -0500 Subject: [PATCH 07/16] thursdays work - nonfunctional still --- src/sbuf.c | 78 +++++++++++++++++++++++++--------------------------- src/server.c | 8 ++++-- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/sbuf.c b/src/sbuf.c index e5e0aeb57e9e..b2bf6f03fe97 100644 --- a/src/sbuf.c +++ b/src/sbuf.c @@ -1324,50 +1324,56 @@ static int gssenc_sbufio_close(struct SBuf *sbuf) static ssize_t gssenc_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len) { - char tmp[16388]; - int token_flags; + gss_buffer_desc recv_buf, unwrap_buf, *msg_buf; + int conf_state, ret, token_flags; + OM_uint32 maj, min; + maj = 0; + min = 0; log_warning("gssenc_sbufio_recv start"); - recv_token(sbuf->sock, &token_flags, &tmp); + ret = recv_token(sbuf->sock, &token_flags, &recv_buf); + if (ret < 0) + return -1; log_warning("gssenc_sbufio_recv token received"); - int maj = 0; - int min = 0; - maj = gss_unwrap(&min, sbuf->gss, &tmp, &dst, NULL, NULL); + maj = gss_unwrap(&min, sbuf->gss, &recv_buf, &unwrap_buf, &conf_state, (gss_qop_t *) NULL); + if (GSS_ERROR(maj)) { + log_warning("gssenc_sbufio_recv - gss_wrap() error major 0x%x minor 0x%x\n", maj, min); + return -1; + } + msg_buf = &unwrap_buf; + dst = msg_buf->value; log_warning("gssenc_sbufio_recv end"); - return 0; + return ret; } static ssize_t gssenc_sbufio_send(struct SBuf *sbuf, const void *data, size_t len) { - log_warning("gssenc_sbufio_send start"); - char tmp[16384]; gss_buffer_desc in_buf, out_buf = GSS_C_EMPTY_BUFFER; + OM_uint32 maj, min; + int state; + log_warning("gssenc_sbufio_send start"); in_buf.length = len; in_buf.value = (char *) data; out_buf.value = NULL; out_buf.length = 0; - OM_uint32 maj = 0; - OM_uint32 min = 0; - int state; - ssize_t out; +// ssize_t out; maj = gss_wrap(&min, sbuf->gss, 1, GSS_C_QOP_DEFAULT, &in_buf, &state, &out_buf); if (GSS_ERROR(maj)) { log_warning("gssenc_sbufio_send - gss_wrap() error major 0x%x minor 0x%x\n", maj, min); return -1; } - out = send_token(sbuf->sock, NULL, &out_buf); +// out = send_token(sbuf->sock, NULL, &out_buf); + send_token(sbuf->sock, NULL, &out_buf); log_warning("gssenc_sbufio_send end"); return len; } -static void -release_buffer(gss_buffer_t buf) +static void release_buffer(gss_buffer_t buf) { free(buf->value); buf->value = NULL; buf->length = 0; } -static int -write_all(int fildes, const void *data, unsigned int nbyte) +static int write_all(int fildes, const void *data, unsigned int nbyte) { int ret; const char *ptr, *buf = data; @@ -1408,11 +1414,7 @@ write_all(int fildes, const void *data, unsigned int nbyte) * if an error occurs or if it could not write all the data. */ -static int -send_token(s, flags, tok) - int s; - int flags; - gss_buffer_t tok; +static int send_token(int s, int flags, gss_buffer_t tok) { int ret; unsigned char char_flags = (unsigned char) flags; @@ -1421,7 +1423,7 @@ send_token(s, flags, tok) if (char_flags) { ret = write_all(s, (char *) &char_flags, 1); if (ret != 1) { - perror("sending token flags"); + log_error("sending token flags"); return -1; } } @@ -1434,7 +1436,7 @@ send_token(s, flags, tok) ret = write_all(s, lenbuf, 4); if (ret < 0) { - perror("sending token length"); + log_error("sending token length"); return -1; } else if (ret != 4) { return -1; @@ -1442,7 +1444,7 @@ send_token(s, flags, tok) ret = write_all(s, tok->value, tok->length); if (ret < 0) { - perror("sending token data"); + log_error("sending token data"); return -1; } else if ((size_t)ret != tok->length) { return -1; @@ -1451,8 +1453,7 @@ send_token(s, flags, tok) return 0; } -static int -read_all(int fildes, void *data, unsigned int nbyte) +static int read_all(int fildes, void *data, unsigned int nbyte) { int ret; char *ptr, *buf = data; @@ -1505,10 +1506,7 @@ read_all(int fildes, void *data, unsigned int nbyte) * and -1 if an error occurs or if it could not read all the data. */ static int -recv_token(s, flags, tok) - int s; - int *flags; - gss_buffer_t tok; +recv_token(int s, int * flags, gss_buffer_t tok) { int ret; unsigned char char_flags; @@ -1516,7 +1514,7 @@ recv_token(s, flags, tok) ret = read_all(s, (char *) &char_flags, 1); if (ret < 0) { - perror("reading token flags"); + log_error("reading token flags"); return -1; } else if (!ret) { return -1; @@ -1528,7 +1526,7 @@ recv_token(s, flags, tok) lenbuf[0] = 0; ret = read_all(s, &lenbuf[1], 3); if (ret < 0) { - perror("reading token length"); + log_error("reading token length"); return -1; } else if (ret != 3) { return -1; @@ -1536,7 +1534,7 @@ recv_token(s, flags, tok) } else { ret = read_all(s, lenbuf, 4); if (ret < 0) { - perror("reading token length"); + log_error("reading token length"); return -1; } else if (ret != 4) { return -1; @@ -1554,7 +1552,7 @@ recv_token(s, flags, tok) ret = read_all(s, (char *) tok->value, tok->length); if (ret < 0) { - perror("reading token data"); + log_error("reading token data"); free(tok->value); return -1; } else if ((size_t)ret != tok->length) { @@ -1564,7 +1562,7 @@ recv_token(s, flags, tok) return -1; } - return 0; + return tok->length+4; } /* @@ -1650,7 +1648,7 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) if (major & GSS_S_CONTINUE_NEEDED) { ret = recv_token(sbuf->sock, &token_flags, &input_token); - if (ret != 0) + if (ret < 0) goto cleanup; } else if (major == GSS_S_COMPLETE) { initiator_established = 1; @@ -1697,7 +1695,7 @@ static bool handle_gssenc_handshake(SBuf *sbuf) { sbuf->gssenc_state = SBUF_GSSENC_OK; sbuf_call_proto(sbuf, SBUF_EV_GSSENC_READY); - return true; + return true; } static void sbuf_gssenc_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf) @@ -1713,7 +1711,7 @@ static void sbuf_gssenc_handshake_cb(evutil_socket_t fd, short flags, void *_sbu //bool sbuf_tls_setup(void) { return true; } //bool sbuf_tls_accept(SBuf *sbuf) { return false; } -bool sbuf_gssenc__connect(SBuf *sbuf, const char *hostname) { return false; } +bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) { return false; } void sbuf_cleanup(void) { diff --git a/src/server.c b/src/server.c index 09de9fd8aedf..c55dd56733df 100644 --- a/src/server.c +++ b/src/server.c @@ -690,11 +690,13 @@ bool server_proto(SBuf *sbuf, SBufEvent evtype, struct MBuf *data) Assert(server->state == SV_LOGIN); // tls_get_connection_info(server->sbuf.tls, infobuf, sizeof infobuf); -// if (cf_log_connections) { + if (cf_log_connections) { // slog_info(server, "SSL established: %s", infobuf); -// } else { + slog_info(server, "GSSENC established"); + } else { // slog_noise(server, "SSL established: %s", infobuf); -// } + slog_noise(server, "GSSENC established"); + } server->request_time = get_cached_time(); res = send_startup_packet(server); From 7578d1181ce4c80528a1a98bad421ee1a618949b Mon Sep 17 00:00:00 2001 From: coryastronomer <107139210+coryastronomer@users.noreply.github.com> Date: Fri, 15 Jul 2022 18:10:41 -0500 Subject: [PATCH 08/16] initial working implementation of gss encryption - synchronous IO mode --- src/admin.c | 6 +++++- src/sbuf.c | 25 ++++++++++++++++--------- src/server.c | 2 +- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/admin.c b/src/admin.c index d693fb85ad7e..27f0c50c6b73 100644 --- a/src/admin.c +++ b/src/admin.c @@ -292,7 +292,7 @@ static bool send_one_fd(PgSocket *admin, msg.msg_iovlen = 1; /* attach a fd */ - if (pga_is_unix(&admin->remote_addr) && admin->own_user && !admin->sbuf.tls) { + if (pga_is_unix(&admin->remote_addr) && admin->own_user && !admin->sbuf.tls && !admin->sbuf.gss) { msg.msg_control = cntbuf; msg.msg_controllen = sizeof(cntbuf); @@ -341,6 +341,10 @@ static bool show_one_fd(PgSocket *admin, PgSocket *sk) if (sk->sbuf.tls || (sk->link && sk->link->sbuf.tls)) return true; + /* Skip GSSENC sockets */ + if (sk->sbuf.gss || (sk->link && sk->link->sbuf.gss)) + return true; + mbuf_init_fixed_reader(&tmp, sk->cancel_key, 8); if (!mbuf_get_uint64be(&tmp, &ckey)) return false; diff --git a/src/sbuf.c b/src/sbuf.c index b2bf6f03fe97..25a3a962088c 100644 --- a/src/sbuf.c +++ b/src/sbuf.c @@ -1327,6 +1327,7 @@ static ssize_t gssenc_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len) gss_buffer_desc recv_buf, unwrap_buf, *msg_buf; int conf_state, ret, token_flags; OM_uint32 maj, min; + socket_set_nonblocking(sbuf_socket(sbuf), 0); maj = 0; min = 0; log_warning("gssenc_sbufio_recv start"); @@ -1339,16 +1340,18 @@ static ssize_t gssenc_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len) log_warning("gssenc_sbufio_recv - gss_wrap() error major 0x%x minor 0x%x\n", maj, min); return -1; } - msg_buf = &unwrap_buf; - dst = msg_buf->value; - log_warning("gssenc_sbufio_recv end"); - return ret; +// msg_buf = &unwrap_buf; +// dst = &(msg_buf->value); + memcpy(dst, unwrap_buf.value, unwrap_buf.length); + log_warning("gssenc_sbufio_recv end %d", unwrap_buf.length); + return unwrap_buf.length; } static ssize_t gssenc_sbufio_send(struct SBuf *sbuf, const void *data, size_t len) { gss_buffer_desc in_buf, out_buf = GSS_C_EMPTY_BUFFER; OM_uint32 maj, min; - int state; + int ret, state; + socket_set_nonblocking(sbuf_socket(sbuf), 0); log_warning("gssenc_sbufio_send start"); in_buf.length = len; in_buf.value = (char *) data; @@ -1361,8 +1364,8 @@ static ssize_t gssenc_sbufio_send(struct SBuf *sbuf, const void *data, size_t le return -1; } // out = send_token(sbuf->sock, NULL, &out_buf); - send_token(sbuf->sock, NULL, &out_buf); - log_warning("gssenc_sbufio_send end"); + ret = send_token(sbuf->sock, NULL, &out_buf); + log_warning("gssenc_sbufio_send end %d\n", len); return len; } @@ -1450,7 +1453,7 @@ static int send_token(int s, int flags, gss_buffer_t tok) return -1; } - return 0; + return tok->length; } static int read_all(int fildes, void *data, unsigned int nbyte) @@ -1581,6 +1584,8 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) gss_name_t target_name = GSS_C_NO_NAME; int token_flags; + socket_set_nonblocking(sbuf_socket(sbuf), 0); + /* Applications should set target_name to a real value. */ name_buf.value = "postgres/kerberized-postgres@EXAMPLE.COM"; name_buf.length = strlen(name_buf.value); @@ -1631,7 +1636,7 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) if ((major & GSS_S_CONTINUE_NEEDED) || output_token.length > 0) { ret = send_token(sbuf->sock, NULL, &output_token); - if (ret != 0) + if (ret < 0) goto cleanup; } /* Check for errors after sending the token so that we will send @@ -1694,6 +1699,8 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) static bool handle_gssenc_handshake(SBuf *sbuf) { sbuf->gssenc_state = SBUF_GSSENC_OK; +// sbuf_use_callback_once(sbuf, EV_READ, sbuf_gssenc_handshake_cb); +// sbuf_use_callback_once(sbuf, EV_WRITE, sbuf_gssenc_handshake_cb); sbuf_call_proto(sbuf, SBUF_EV_GSSENC_READY); return true; } diff --git a/src/server.c b/src/server.c index c55dd56733df..5a1c1a3b95ff 100644 --- a/src/server.c +++ b/src/server.c @@ -462,7 +462,7 @@ static bool handle_connect(PgSocket *server) res = send_sslreq_packet(server); if (res) server->wait_sslchar = true; - } else if (!is_unix) { // TODO: make it work like SSLMODE_ENABLED above + } else if (!is_unix) { // TODO: make it work like SSLMODE_ENABLED above slog_noise(server, "P: GSSEnc request"); res = send_gssencreq_packet(server); if (res) From 137896e37ba70c185a9c5d90e764206f36f12c47 Mon Sep 17 00:00:00 2001 From: coryastronomer <107139210+coryastronomer@users.noreply.github.com> Date: Wed, 27 Jul 2022 10:44:23 -0500 Subject: [PATCH 09/16] fix compilation warnings --- src/sbuf.c | 108 ++++++++++++++++++++--------------------------------- 1 file changed, 41 insertions(+), 67 deletions(-) diff --git a/src/sbuf.c b/src/sbuf.c index 25a3a962088c..58388d5c5bf6 100644 --- a/src/sbuf.c +++ b/src/sbuf.c @@ -89,7 +89,9 @@ static bool sbuf_call_proto(SBuf *sbuf, int event) /* _MUSTCHECK */; static bool sbuf_actual_recv(SBuf *sbuf, size_t len) _MUSTCHECK; static bool sbuf_after_connect_check(SBuf *sbuf) _MUSTCHECK; static bool handle_tls_handshake(SBuf *sbuf) /* _MUSTCHECK */; +#ifdef HAVE_SERVER_GSSENC static bool handle_gssenc_handshake(SBuf *sbuf) /* _MUSTCHECK */; +#endif /* regular I/O */ static ssize_t raw_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len); @@ -126,7 +128,7 @@ static const SBufIO gssenc_sbufio_ops = { }; static int recv_token(int s, int *flags, gss_buffer_t tok); static int send_token(int s, int flags, gss_buffer_t tok); -static void sbuf_gssenc_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf); +//static void sbuf_gssenc_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf); #endif /********************************* @@ -803,10 +805,12 @@ static void sbuf_main_loop(SBuf *sbuf, bool skip_recv) sbuf->pkt_action = SBUF_TLS_IN_HANDSHAKE; handle_tls_handshake(sbuf); } +#ifdef HAVE_SERVER_GSSENC if (sbuf->gssenc_state == SBUF_GSSENC_DO_HANDSHAKE) { sbuf->pkt_action = SBUF_GSSENC_IN_HANDSHAKE; handle_gssenc_handshake(sbuf); } +#endif } /* check if there is any error pending on socket */ @@ -1271,6 +1275,7 @@ static int tls_sbufio_close(struct SBuf *sbuf) return 0; } +// TODO: handle gssapi somehow, respecting macros etc void sbuf_cleanup(void) { tls_free(client_accept_base); @@ -1324,49 +1329,49 @@ static int gssenc_sbufio_close(struct SBuf *sbuf) static ssize_t gssenc_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len) { - gss_buffer_desc recv_buf, unwrap_buf, *msg_buf; + gss_buffer_desc recv_buf = GSS_C_EMPTY_BUFFER; + gss_buffer_desc unwrap_buf = GSS_C_EMPTY_BUFFER; int conf_state, ret, token_flags; OM_uint32 maj, min; socket_set_nonblocking(sbuf_socket(sbuf), 0); maj = 0; min = 0; - log_warning("gssenc_sbufio_recv start"); + log_noise("gssenc_sbufio_recv start"); ret = recv_token(sbuf->sock, &token_flags, &recv_buf); if (ret < 0) return -1; - log_warning("gssenc_sbufio_recv token received"); + log_noise("gssenc_sbufio_recv token received"); maj = gss_unwrap(&min, sbuf->gss, &recv_buf, &unwrap_buf, &conf_state, (gss_qop_t *) NULL); if (GSS_ERROR(maj)) { log_warning("gssenc_sbufio_recv - gss_wrap() error major 0x%x minor 0x%x\n", maj, min); return -1; } -// msg_buf = &unwrap_buf; -// dst = &(msg_buf->value); memcpy(dst, unwrap_buf.value, unwrap_buf.length); - log_warning("gssenc_sbufio_recv end %d", unwrap_buf.length); + log_noise("gssenc_sbufio_recv end %d", (int) unwrap_buf.length); return unwrap_buf.length; } + static ssize_t gssenc_sbufio_send(struct SBuf *sbuf, const void *data, size_t len) { - gss_buffer_desc in_buf, out_buf = GSS_C_EMPTY_BUFFER; - OM_uint32 maj, min; - int ret, state; - socket_set_nonblocking(sbuf_socket(sbuf), 0); - log_warning("gssenc_sbufio_send start"); - in_buf.length = len; - in_buf.value = (char *) data; - out_buf.value = NULL; - out_buf.length = 0; -// ssize_t out; - maj = gss_wrap(&min, sbuf->gss, 1, GSS_C_QOP_DEFAULT, &in_buf, &state, &out_buf); - if (GSS_ERROR(maj)) { - log_warning("gssenc_sbufio_send - gss_wrap() error major 0x%x minor 0x%x\n", maj, min); - return -1; - } -// out = send_token(sbuf->sock, NULL, &out_buf); - ret = send_token(sbuf->sock, NULL, &out_buf); - log_warning("gssenc_sbufio_send end %d\n", len); - return len; + gss_buffer_desc in_buf, out_buf = GSS_C_EMPTY_BUFFER; + OM_uint32 maj, min; + int ret, state; + socket_set_nonblocking(sbuf_socket(sbuf), 0); + log_noise("gssenc_sbufio_send start"); + in_buf.length = len; + in_buf.value = (char *) data; + out_buf.value = NULL; + out_buf.length = 0; + maj = gss_wrap(&min, sbuf->gss, 1, GSS_C_QOP_DEFAULT, &in_buf, &state, &out_buf); + if (GSS_ERROR(maj)) { + log_noise("gssenc_sbufio_send - gss_wrap() error major 0x%x minor 0x%x\n", maj, min); + return -1; + } + ret = send_token(sbuf->sock, 0, &out_buf); + if (ret < 0) + log_error("gssenc_sbufio_send ret %d\n", ret); + log_noise("gssenc_sbufio_send end %d\n", (int) len); + return (ssize_t) len; } static void release_buffer(gss_buffer_t buf) @@ -1574,7 +1579,6 @@ recv_token(int s, int * flags, gss_buffer_t tok) bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) { -// int err; int initiator_established = 0, ret; gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; OM_uint32 major, minor, req_flags, ret_flags; @@ -1599,19 +1603,6 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) if (!sbuf_pause(sbuf)) return false; -// if (cf_server_tls_sslmode != SSLMODE_VERIFY_FULL) -// hostname = NULL; - -// ctls = tls_client(); -// if (!ctls) -// return false; -// err = tls_configure(ctls, server_connect_conf); -// if (err < 0) { -// log_error("tls client config failed: %s", tls_error(ctls)); -// tls_free(ctls); -// return false; -// } - /* Mutual authentication will require a token from acceptor to * initiator and thus a second call to gss_init_sec_context(). */ req_flags = GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG; @@ -1635,7 +1626,8 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) * (GSS_S_CONTINUE_NEEDED is set) or if it is nonempty. */ if ((major & GSS_S_CONTINUE_NEEDED) || output_token.length > 0) { - ret = send_token(sbuf->sock, NULL, &output_token); +// ret = send_token(sbuf->sock, NULL, &output_token); + ret = send_token(sbuf->sock, 0, &output_token); if (ret < 0) goto cleanup; } @@ -1670,16 +1662,7 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) log_noise("Initiator's context negotiation successful\n"); sbuf->gss = ctx; -// sbuf->tls = ctls; -// sbuf->tls_host = hostname; sbuf->ops = &gssenc_sbufio_ops; -// sbuf->ops = &tls_sbufio_ops; - -// err = tls_connect_fds(sbuf->tls, sbuf->sock, sbuf->sock, sbuf->tls_host); -// if (err < 0) { -// log_warning("GSS connect error: %s", tls_error(sbuf->tls)); -// return false; -// } sbuf->gssenc_state = SBUF_GSSENC_DO_HANDSHAKE; return true; @@ -1705,13 +1688,13 @@ static bool handle_gssenc_handshake(SBuf *sbuf) return true; } -static void sbuf_gssenc_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf) -{ - SBuf *sbuf = _sbuf; - sbuf->wait_type = W_NONE; - if (!handle_gssenc_handshake(sbuf)) - sbuf_call_proto(sbuf, SBUF_EV_RECV_FAILED); -} +//static void sbuf_gssenc_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf) +//{ +// SBuf *sbuf = _sbuf; +// sbuf->wait_type = W_NONE; +// if (!handle_gssenc_handshake(sbuf)) +// sbuf_call_proto(sbuf, SBUF_EV_RECV_FAILED); +//} #else //int client_accept_sslmode = SSLMODE_DISABLED; //int server_connect_sslmode = SSLMODE_DISABLED; @@ -1719,13 +1702,4 @@ static void sbuf_gssenc_handshake_cb(evutil_socket_t fd, short flags, void *_sbu //bool sbuf_tls_setup(void) { return true; } //bool sbuf_tls_accept(SBuf *sbuf) { return false; } bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) { return false; } - -void sbuf_cleanup(void) -{ -} - -static bool handle_gssenc_handshake(SBuf *sbuf) -{ - return false; -} -#endif +#endif \ No newline at end of file From ab86ca9fc369f8ba0073ecb434c675e44f0ff8ed Mon Sep 17 00:00:00 2001 From: coryastronomer <107139210+coryastronomer@users.noreply.github.com> Date: Thu, 28 Jul 2022 11:43:35 -0500 Subject: [PATCH 10/16] initial working async io commit --- include/sbuf.h | 27 ++ src/sbuf.c | 664 +++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 676 insertions(+), 15 deletions(-) diff --git a/include/sbuf.h b/include/sbuf.h index 933fa45d462a..85f92796f109 100644 --- a/include/sbuf.h +++ b/include/sbuf.h @@ -43,6 +43,13 @@ typedef enum { */ #define SBUF_SMALL_PKT 64 +#ifdef HAVE_SERVER_GSSENC + +#define GSSENC_WANT_POLLOUT -3 +#define GSSENC_WANT_POLLIN -2 + +#endif + struct tls; /* fwd def */ @@ -68,6 +75,8 @@ struct SBufIO { * Stream is divided to packets. On each packet start * protocol handler is called that decides what to do. */ +typedef unsigned int uint32; /* == 32 bits */ + struct SBuf { struct event ev; /* libevent handle */ @@ -89,6 +98,24 @@ struct SBuf { const SBufIO *ops; /* normal vs. TLS vs. GSS */ struct tls *tls; /* TLS context */ struct gss_ctx_id_struct *gss; +#ifdef HAVE_SERVER_GSSENC + char *gss_SendBuffer; /* Encrypted data waiting to be sent */ + int gss_SendLength; /* End of data available in gss_SendBuffer */ + int gss_SendNext; /* Next index to send a byte from + * gss_SendBuffer */ + int gss_SendConsumed; /* Number of *unencrypted* bytes consumed + * for current contents of gss_SendBuffer */ + char *gss_RecvBuffer; /* Received, encrypted data */ + int gss_RecvLength; /* End of data available in gss_RecvBuffer */ + char *gss_ResultBuffer; /* Decryption of data in gss_RecvBuffer */ + int gss_ResultLength; /* End of data available in + * gss_ResultBuffer */ + int gss_ResultNext; /* Next index to read a byte from + * gss_ResultBuffer */ + uint32 gss_MaxPktSize; /* Maximum size we can encrypt and fit the + * results into our output buffer */ + bool write_failed; /* have we had a write failure on sock? */ +#endif const char *tls_host; /* target hostname */ }; diff --git a/src/sbuf.c b/src/sbuf.c index 58388d5c5bf6..33f8c9e77a94 100644 --- a/src/sbuf.c +++ b/src/sbuf.c @@ -29,6 +29,8 @@ #include #include +//#include + #ifdef USUAL_LIBSSL_FOR_TLS #define USE_TLS #endif @@ -118,6 +120,12 @@ static void sbuf_tls_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf); /* I/O over GSS Enc */ #ifdef HAVE_SERVER_GSSENC +#define Min(x, y) ((x) < (y) ? (x) : (y)) +static ssize_t pg_GSS_write(SBuf *conn, const void *ptr, size_t len); +static ssize_t pqsecure_raw_write(SBuf *conn, const void *ptr, size_t len); +static ssize_t pg_GSS_read(SBuf *conn, void *ptr, size_t len); +/*static ssize_t gss_read(SBuf *conn, void *recv_buffer, size_t length, ssize_t *ret);*/ +static ssize_t pqsecure_raw_read(SBuf *conn, void *ptr, size_t len); static ssize_t gssenc_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len); static ssize_t gssenc_sbufio_send(struct SBuf *sbuf, const void *data, size_t len); static int gssenc_sbufio_close(struct SBuf *sbuf); @@ -796,7 +804,6 @@ static void sbuf_main_loop(SBuf *sbuf, bool skip_recv) /* clean buffer */ sbuf_try_resync(sbuf, true); - /* notify proto that all is sent */ if (sbuf_is_empty(sbuf)) sbuf_call_proto(sbuf, SBUF_EV_FLUSH); @@ -1327,6 +1334,7 @@ static int gssenc_sbufio_close(struct SBuf *sbuf) return 0; } +/* static ssize_t gssenc_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len) { gss_buffer_desc recv_buf = GSS_C_EMPTY_BUFFER; @@ -1350,8 +1358,34 @@ static ssize_t gssenc_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len) log_noise("gssenc_sbufio_recv end %d", (int) unwrap_buf.length); return unwrap_buf.length; } +*/ -static ssize_t gssenc_sbufio_send(struct SBuf *sbuf, const void *data, size_t len) +static ssize_t gssenc_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len) +{ + ssize_t out = 0; + + if (sbuf->gssenc_state != SBUF_GSSENC_OK) { + errno = EIO; + return -1; + } + + out = pg_GSS_read(sbuf, dst, len); + log_noise("pg_GSS_read: req=%zu out=%zd", len, out); + if (out >= 0) { + return out; + } else if (out == GSSENC_WANT_POLLIN) { + errno = EAGAIN; + } else if (out == GSSENC_WANT_POLLOUT) { + log_warning("gssenc_sbufio_recv: got GSSENC_WANT_POLLOUT"); + errno = EIO; + } else { + log_warning("gssenc_sbufio_recv: error"); + errno = EIO; + } + return -1; +} + +/*static ssize_t gssenc_sbufio_send(struct SBuf *sbuf, const void *data, size_t len) { gss_buffer_desc in_buf, out_buf = GSS_C_EMPTY_BUFFER; OM_uint32 maj, min; @@ -1372,6 +1406,582 @@ static ssize_t gssenc_sbufio_send(struct SBuf *sbuf, const void *data, size_t le log_error("gssenc_sbufio_send ret %d\n", ret); log_noise("gssenc_sbufio_send end %d\n", (int) len); return (ssize_t) len; +}*/ + +static ssize_t gssenc_sbufio_send(struct SBuf *sbuf, const void *data, size_t len) +{ + ssize_t out; + + if (sbuf->gssenc_state != SBUF_GSSENC_OK) { + errno = EIO; + return -1; + } + + out = pg_GSS_write(sbuf, data, len); + log_noise("pg_GSS_write: req=%zu out=%zd", len, out); + if (out >= 0) { + return out; + } else if (out == GSSENC_WANT_POLLOUT) { + errno = EAGAIN; + } else if (out == GSSENC_WANT_POLLIN) { + log_warning("gssenc_sbufio_send: got GSSENC_WANT_POLLIN"); + errno = EIO; + } else { + log_warning("gssenc_sbufio_send: EIO"); + errno = EIO; + } + return -1; +} +/* + * Require encryption support, as well as mutual authentication and + * tamperproofing measures. + */ +#define GSS_REQUIRED_FLAGS GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | \ + GSS_C_SEQUENCE_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG + +/* + * Handle the encryption/decryption of data using GSSAPI. + * + * In the encrypted data stream on the wire, we break up the data + * into packets where each packet starts with a uint32-size length + * word (in network byte order), then encrypted data of that length + * immediately following. Decryption yields the same data stream + * that would appear when not using encryption. + * + * Encrypted data typically ends up being larger than the same data + * unencrypted, so we use fixed-size buffers for handling the + * encryption/decryption which are larger than PQComm's buffer will + * typically be to minimize the times where we have to make multiple + * packets (and therefore multiple recv/send calls for a single + * read/write call to us). + * + * NOTE: The client and server have to agree on the max packet size, + * because we have to pass an entire packet to GSSAPI at a time and we + * don't want the other side to send arbitrarily huge packets as we + * would have to allocate memory for them to then pass them to GSSAPI. + * + * Therefore, these two #define's are effectively part of the protocol + * spec and can't ever be changed. + */ +#define PQ_GSS_SEND_BUFFER_SIZE 16384 +#define PQ_GSS_RECV_BUFFER_SIZE 16384 + +/* + * We need these state variables per-connection. To allow the functions + * in this file to look mostly like those in be-secure-gssapi.c, set up + * these macros. + */ +#define PqGSSSendBuffer (conn->gss_SendBuffer) +#define PqGSSSendLength (conn->gss_SendLength) +#define PqGSSSendNext (conn->gss_SendNext) +#define PqGSSSendConsumed (conn->gss_SendConsumed) +#define PqGSSRecvBuffer (conn->gss_RecvBuffer) +#define PqGSSRecvLength (conn->gss_RecvLength) +#define PqGSSResultBuffer (conn->gss_ResultBuffer) +#define PqGSSResultLength (conn->gss_ResultLength) +#define PqGSSResultNext (conn->gss_ResultNext) +#define PqGSSMaxPktSize (conn->gss_MaxPktSize) + +/* + * Attempt to write len bytes of data from ptr to a GSSAPI-encrypted connection. + * + * The connection must be already set up for GSSAPI encryption (i.e., GSSAPI + * transport negotiation is complete). + * + * On success, returns the number of data bytes consumed (possibly less than + * len). On failure, returns -1 with errno set appropriately. If the errno + * indicates a non-retryable error, a message is added to conn->errorMessage. + * For retryable errors, caller should call again (passing the same data) + * once the socket is ready. + */ +static ssize_t +pg_GSS_write(SBuf *conn, const void *ptr, size_t len) +{ + OM_uint32 major, + minor; + gss_buffer_desc input, + output = GSS_C_EMPTY_BUFFER; + ssize_t ret = -1; + size_t bytes_sent = 0; + size_t bytes_to_encrypt; + size_t bytes_encrypted; + gss_ctx_id_t gctx = conn->gss; + + /* + * When we get a failure, we must not tell the caller we have successfully + * transmitted everything, else it won't retry. Hence a "success" + * (positive) return value must only count source bytes corresponding to + * fully-transmitted encrypted packets. The amount of source data + * corresponding to the current partly-transmitted packet is remembered in + * PqGSSSendConsumed. On a retry, the caller *must* be sending that data + * again, so if it offers a len less than that, something is wrong. + */ + if (len < ((unsigned int)PqGSSSendConsumed)) + { + log_error("GSSAPI caller failed to retransmit all data needing to be retried"); + errno = EINVAL; + return -1; + } + + /* Discount whatever source data we already encrypted. */ + bytes_to_encrypt = len - PqGSSSendConsumed; + bytes_encrypted = PqGSSSendConsumed; + + /* + * Loop through encrypting data and sending it out until it's all done or + * pqsecure_raw_write() complains (which would likely mean that the socket + * is non-blocking and the requested send() would block, or there was some + * kind of actual error). + */ + while (bytes_to_encrypt || PqGSSSendLength) + { + int conf_state = 0; + uint32 netlen; + + /* + * Check if we have data in the encrypted output buffer that needs to + * be sent (possibly left over from a previous call), and if so, try + * to send it. If we aren't able to, return that fact back up to the + * caller. + */ + if (PqGSSSendLength) + { + ssize_t ret; + ssize_t amount = PqGSSSendLength - PqGSSSendNext; + + ret = pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendNext, amount); + if (ret <= 0) + { + /* + * Report any previously-sent data; if there was none, reflect + * the pqsecure_raw_write result up to our caller. When there + * was some, we're effectively assuming that any interesting + * failure condition will recur on the next try. + */ + if (bytes_sent) + return bytes_sent; + return ret; + } + + /* + * Check if this was a partial write, and if so, move forward that + * far in our buffer and try again. + */ + if (ret != amount) + { + PqGSSSendNext += ret; + continue; + } + + /* We've successfully sent whatever data was in that packet. */ + bytes_sent += PqGSSSendConsumed; + + /* All encrypted data was sent, our buffer is empty now. */ + PqGSSSendLength = PqGSSSendNext = PqGSSSendConsumed = 0; + } + + /* + * Check if there are any bytes left to encrypt. If not, we're done. + */ + if (!bytes_to_encrypt) + break; + + /* + * Check how much we are being asked to send, if it's too much, then + * we will have to loop and possibly be called multiple times to get + * through all the data. + */ + if (bytes_to_encrypt > PqGSSMaxPktSize) + input.length = PqGSSMaxPktSize; + else + input.length = bytes_to_encrypt; + + input.value = (char *) ptr + bytes_encrypted; + + output.value = NULL; + output.length = 0; + + /* + * Create the next encrypted packet. Any failure here is considered a + * hard failure, so we return -1 even if bytes_sent > 0. + */ + major = gss_wrap(&minor, gctx, 1, GSS_C_QOP_DEFAULT, + &input, &conf_state, &output); + if (major != GSS_S_COMPLETE) + { +// pg_GSS_error(libpq_gettext("GSSAPI wrap error"), conn, major, minor); + log_error("GSSAPI wrap error major 0x%u, minor 0x%u", major, minor); + errno = EIO; /* for lack of a better idea */ + goto cleanup; + } + + if (conf_state == 0) + { + log_error("outgoing GSSAPI message would not use confidentiality"); + errno = EIO; /* for lack of a better idea */ + goto cleanup; + } + + if (output.length > PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)) + { + log_error("client tried to send oversize GSSAPI packet (%zu > %zu)", + (size_t) output.length, + PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32)); + errno = EIO; /* for lack of a better idea */ + goto cleanup; + } + + bytes_encrypted += input.length; + bytes_to_encrypt -= input.length; + PqGSSSendConsumed += input.length; + + /* 4 network-order bytes of length, then payload */ + netlen = htonl(output.length); + memcpy(PqGSSSendBuffer + PqGSSSendLength, &netlen, sizeof(uint32)); + PqGSSSendLength += sizeof(uint32); + + memcpy(PqGSSSendBuffer + PqGSSSendLength, output.value, output.length); + PqGSSSendLength += output.length; + + /* Release buffer storage allocated by GSSAPI */ + gss_release_buffer(&minor, &output); + } + + /* If we get here, our counters should all match up. */ + Assert(bytes_sent == len); + Assert(bytes_sent == bytes_encrypted); + + ret = bytes_sent; + +cleanup: + /* Release GSSAPI buffer storage, if we didn't already */ + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +/* + * Low-level implementation of pqsecure_write. + * + * This is used directly for an unencrypted connection. For encrypted + * connections, this does the physical I/O on behalf of pgtls_write or + * pg_GSS_write. + * + * This function reports failure (i.e., returns a negative result) only + * for retryable errors such as EINTR. Looping for such cases is to be + * handled at some outer level, maybe all the way up to the application. + * For hard failures, we set conn->write_failed and store an error message + * in conn->write_err_msg, but then claim to have written the data anyway. + * This is because we don't want to report write failures so long as there + * is a possibility of reading from the server and getting an error message + * that could explain why the connection dropped. Many TCP stacks have + * race conditions such that a write failure may or may not be reported + * before all incoming data has been read. + * + * Note that this error behavior happens below the SSL management level when + * we are using SSL. That's because at least some versions of OpenSSL are + * too quick to report a write failure when there's still a possibility to + * get a more useful error from the server. + */ +static ssize_t +pqsecure_raw_write(SBuf *conn, const void *ptr, size_t len) +{ + ssize_t n; + int flags = 0; + int result_errno = 0; + + /* + * If we already had a write failure, we will never again try to send data + * on that connection. Even if the kernel would let us, we've probably + * lost message boundary sync with the server. conn->write_failed + * therefore persists until the connection is reset, and we just discard + * all data presented to be written. + */ + if (conn->write_failed) + return len; + + n = send(conn->sock, ptr, len, flags); + + if (n < 0) + { + result_errno = errno; + + /* Set error message if appropriate */ + switch (result_errno) + { + case EAGAIN: + errno = result_errno; + return GSSENC_WANT_POLLOUT; + case EINTR: + /* no error message, caller is expected to retry */ + break; + + case ECONNRESET: + conn->write_failed = true; + /* Store error message in conn->write_err_msg, if possible */ + /* (strdup failure is OK, we'll cope later) */ + log_error("server closed the connection unexpectedly\n" + "\tThis probably means the server terminated abnormally\n" + "\tbefore or while processing the request.\n"); + /* Now claim the write succeeded */ + n = len; + break; + + default: + conn->write_failed = true; + /* Store error message in conn->write_err_msg, if possible */ + /* (strdup failure is OK, we'll cope later) */ + log_error("could not send data to server"); + /* Now claim the write succeeded */ + n = len; + break; + } + } + + /* ensure we return the intended errno to caller */ + errno = result_errno; + + return n; +} + +/* + * Read up to len bytes of data into ptr from a GSSAPI-encrypted connection. + * + * The connection must be already set up for GSSAPI encryption (i.e., GSSAPI + * transport negotiation is complete). + * + * Returns the number of data bytes read, or on failure, returns -1 + * with errno set appropriately. If the errno indicates a non-retryable + * error, a message is added to conn->errorMessage. For retryable errors, + * caller should call again once the socket is ready. + */ +static ssize_t +pg_GSS_read(SBuf *conn, void *ptr, size_t len) +{ + OM_uint32 major, + minor; + gss_buffer_desc input = GSS_C_EMPTY_BUFFER, + output = GSS_C_EMPTY_BUFFER; + ssize_t ret; + size_t bytes_returned = 0; + gss_ctx_id_t gctx = conn->gss; + + /* + * The plan here is to read one incoming encrypted packet into + * PqGSSRecvBuffer, decrypt it into PqGSSResultBuffer, and then dole out + * data from there to the caller. When we exhaust the current input + * packet, read another. + */ + while (bytes_returned < len) + { + int conf_state = 0; + + /* Check if we have data in our buffer that we can return immediately */ + if (PqGSSResultNext < PqGSSResultLength) + { + size_t bytes_in_buffer = PqGSSResultLength - PqGSSResultNext; + size_t bytes_to_copy = Min(bytes_in_buffer, len - bytes_returned); + + /* + * Copy the data from our result buffer into the caller's buffer, + * at the point where we last left off filling their buffer. + */ + memcpy((char *) ptr + bytes_returned, PqGSSResultBuffer + PqGSSResultNext, bytes_to_copy); + PqGSSResultNext += bytes_to_copy; + bytes_returned += bytes_to_copy; + + /* + * At this point, we've either filled the caller's buffer or + * emptied our result buffer. Either way, return to caller. In + * the second case, we could try to read another encrypted packet, + * but the odds are good that there isn't one available. (If this + * isn't true, we chose too small a max packet size.) In any + * case, there's no harm letting the caller process the data we've + * already returned. + */ + break; + } + + /* Result buffer is empty, so reset buffer pointers */ + PqGSSResultLength = PqGSSResultNext = 0; + + /* + * Because we chose above to return immediately as soon as we emit + * some data, bytes_returned must be zero at this point. Therefore + * the failure exits below can just return -1 without worrying about + * whether we already emitted some data. + */ + Assert(bytes_returned == 0); + + /* + * At this point, our result buffer is empty with more bytes being + * requested to be read. We are now ready to load the next packet and + * decrypt it (entirely) into our result buffer. + */ + + /* Collect the length if we haven't already */ + if (PqGSSRecvLength < (int) sizeof(uint32)) + { + ret = pqsecure_raw_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, + sizeof(uint32) - PqGSSRecvLength); + + /* If ret <= 0, pqsecure_raw_read already set the correct errno */ + if (ret <= 0) + return ret; + + PqGSSRecvLength += ret; + + /* If we still haven't got the length, return to the caller */ + if (PqGSSRecvLength < (int) sizeof(uint32)) + { + errno = EWOULDBLOCK; + return GSSENC_WANT_POLLIN; + } + } + + /* Decode the packet length and check for overlength packet */ + input.length = ntohl(*(uint32 *) PqGSSRecvBuffer); + + if (input.length > PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)) + { + log_error("oversize GSSAPI packet sent by the server (%zu > %zu)", + (size_t) input.length, + PQ_GSS_RECV_BUFFER_SIZE - sizeof(uint32)); + errno = EIO; /* for lack of a better idea */ + return -1; + } + + /* + * Read as much of the packet as we are able to on this call into + * wherever we left off from the last time we were called. + */ + ret = pqsecure_raw_read(conn, PqGSSRecvBuffer + PqGSSRecvLength, + input.length - (PqGSSRecvLength - sizeof(uint32))); + /* If ret <= 0, pqsecure_raw_read already set the correct errno */ + if (ret <= 0) + return ret; + + PqGSSRecvLength += ret; + + /* If we don't yet have the whole packet, return to the caller */ + if (PqGSSRecvLength - sizeof(uint32) < input.length) + { + errno = EWOULDBLOCK; + return GSSENC_WANT_POLLIN; + } + + /* + * We now have the full packet and we can perform the decryption and + * refill our result buffer, then loop back up to pass data back to + * the caller. Note that error exits below here must take care of + * releasing the gss output buffer. + */ + output.value = NULL; + output.length = 0; + input.value = PqGSSRecvBuffer + sizeof(uint32); + + major = gss_unwrap(&minor, gctx, &input, &output, &conf_state, NULL); + if (major != GSS_S_COMPLETE) + { + log_error("GSSAPI unwrap error major 0x%u, minor 0x%u", major, minor); + ret = -1; + errno = EIO; /* for lack of a better idea */ + goto cleanup; + } + + if (conf_state == 0) + { + log_error("incoming GSSAPI message did not use confidentiality"); + ret = -1; + errno = EIO; /* for lack of a better idea */ + goto cleanup; + } + + memcpy(PqGSSResultBuffer, output.value, output.length); + PqGSSResultLength = output.length; + + /* Our receive buffer is now empty, reset it */ + PqGSSRecvLength = 0; + + /* Release buffer storage allocated by GSSAPI */ + gss_release_buffer(&minor, &output); + } + + ret = bytes_returned; + +cleanup: + /* Release GSSAPI buffer storage, if we didn't already */ + if (output.value != NULL) + gss_release_buffer(&minor, &output); + return ret; +} + +/* + * Simple wrapper for reading from pqsecure_raw_read. + * + * This takes the same arguments as pqsecure_raw_read, plus an output parameter + * to return the number of bytes read. This handles if blocking would occur and + * if we detect EOF on the connection. + */ +/* +static ssize_t gss_read(SBuf *conn, void *recv_buffer, size_t length, ssize_t *ret) +{ + *ret = pqsecure_raw_read(conn, recv_buffer, length); + if (*ret < 0) + { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) + return GSSENC_WANT_POLLIN; + else + return -1; + } + + // Check for EOF + if (*ret == 0) + return GSSENC_WANT_POLLIN; + + return *ret; +} +*/ + +static ssize_t +pqsecure_raw_read(SBuf *conn, void *ptr, size_t len) +{ + ssize_t n; + int result_errno = 0; + + n = recv(conn->sock, ptr, len, 0); + + if (n < 0) + { + result_errno = errno; + + /* Set error message if appropriate */ + switch (result_errno) + { + case EAGAIN: + case EINTR: + /* no error message, caller is expected to retry */ + result_errno = errno; + return GSSENC_WANT_POLLIN; + break; + + case EPIPE: + case ECONNRESET: + log_error("server closed the connection unexpectedly\n" + "\tThis probably means the server terminated abnormally\n" + "\tbefore or while processing the request.\n"); + break; + + default: + log_error("could not receive data from server"); + break; + } + } + + /* ensure we return the intended errno to caller */ + errno = result_errno; + + return n; } static void release_buffer(gss_buffer_t buf) @@ -1577,18 +2187,32 @@ recv_token(int s, int * flags, gss_buffer_t tok) * Connect to remote GSS Enc host. */ -bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) +bool sbuf_gssenc_connect(SBuf *conn, const char *hostname) { int initiator_established = 0, ret; gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; - OM_uint32 major, minor, req_flags, ret_flags; + OM_uint32 major, minor, ret_flags; gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; gss_buffer_desc name_buf = GSS_C_EMPTY_BUFFER; gss_name_t target_name = GSS_C_NO_NAME; int token_flags; - socket_set_nonblocking(sbuf_socket(sbuf), 0); + socket_set_nonblocking(sbuf_socket(conn), 0); + + if (PqGSSSendBuffer == NULL) + { + PqGSSSendBuffer = malloc(PQ_GSS_SEND_BUFFER_SIZE); + PqGSSRecvBuffer = malloc(PQ_GSS_RECV_BUFFER_SIZE); + PqGSSResultBuffer = malloc(PQ_GSS_RECV_BUFFER_SIZE); + if (!PqGSSSendBuffer || !PqGSSRecvBuffer || !PqGSSResultBuffer) + { + log_error("out of memory"); + return false; + } + PqGSSSendLength = PqGSSSendNext = PqGSSSendConsumed = 0; + PqGSSRecvLength = PqGSSResultLength = PqGSSResultNext = 0; + } /* Applications should set target_name to a real value. */ name_buf.value = "postgres/kerberized-postgres@EXAMPLE.COM"; @@ -1600,12 +2224,11 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) return false; } - if (!sbuf_pause(sbuf)) + if (!sbuf_pause(conn)) return false; /* Mutual authentication will require a token from acceptor to * initiator and thus a second call to gss_init_sec_context(). */ - req_flags = GSS_C_MUTUAL_FLAG | GSS_C_CONF_FLAG | GSS_C_INTEG_FLAG; while (!initiator_established) { /* The initiator_cred_handle, mech_type, time_req, @@ -1615,7 +2238,7 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) * for them, respectively. */ major = gss_init_sec_context(&minor, GSS_C_NO_CREDENTIAL, &ctx, target_name, GSS_C_NO_OID, - req_flags, 0, NULL, &input_token, + GSS_REQUIRED_FLAGS, 0, NULL, &input_token, NULL, &output_token, &ret_flags, NULL); /* This was allocated by recv_token() and is no longer @@ -1626,8 +2249,8 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) * (GSS_S_CONTINUE_NEEDED is set) or if it is nonempty. */ if ((major & GSS_S_CONTINUE_NEEDED) || output_token.length > 0) { -// ret = send_token(sbuf->sock, NULL, &output_token); - ret = send_token(sbuf->sock, 0, &output_token); +// ret = send_token(conn->sock, NULL, &output_token); + ret = send_token(conn->sock, 0, &output_token); if (ret < 0) goto cleanup; } @@ -1644,7 +2267,7 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) (void)gss_release_buffer(&minor, &output_token); if (major & GSS_S_CONTINUE_NEEDED) { - ret = recv_token(sbuf->sock, &token_flags, &input_token); + ret = recv_token(conn->sock, &token_flags, &input_token); if (ret < 0) goto cleanup; } else if (major == GSS_S_COMPLETE) { @@ -1655,16 +2278,27 @@ bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) goto cleanup; } } /* while (!initiator_established) */ - if ((ret_flags & req_flags) != req_flags) { + if ((ret_flags & (GSS_REQUIRED_FLAGS)) != (GSS_REQUIRED_FLAGS)) { log_warning("Negotiated context does not support requested flags\n"); goto cleanup; } log_noise("Initiator's context negotiation successful\n"); - sbuf->gss = ctx; - sbuf->ops = &gssenc_sbufio_ops; + conn->gss = ctx; + conn->ops = &gssenc_sbufio_ops; - sbuf->gssenc_state = SBUF_GSSENC_DO_HANDSHAKE; + // Turn async back on + socket_set_nonblocking(sbuf_socket(conn), 1); + + conn->gssenc_state = SBUF_GSSENC_DO_HANDSHAKE; + /* + * Determine the max packet size which will fit in our buffer, after + * accounting for the length. pg_GSS_write will need this. + */ + major = gss_wrap_size_limit(&minor, conn->gss, 1, GSS_C_QOP_DEFAULT, + PQ_GSS_SEND_BUFFER_SIZE - sizeof(uint32), + &PqGSSMaxPktSize); + log_noise("Max packet size: %u", PqGSSMaxPktSize); return true; cleanup: From 0290110e945a6f608580deed5beae02a76611833 Mon Sep 17 00:00:00 2001 From: coryastronomer <107139210+coryastronomer@users.noreply.github.com> Date: Mon, 1 Aug 2022 17:42:56 -0500 Subject: [PATCH 11/16] some general cleanup as well as added gssapi_spn option to [database] entries --- include/bouncer.h | 1 + include/sbuf.h | 2 +- src/janitor.c | 1 + src/loader.c | 12 +++++++++++ src/sbuf.c | 55 +++-------------------------------------------- src/server.c | 2 +- 6 files changed, 19 insertions(+), 54 deletions(-) diff --git a/include/bouncer.h b/include/bouncer.h index a31ed4c167da..67080e70d75c 100644 --- a/include/bouncer.h +++ b/include/bouncer.h @@ -363,6 +363,7 @@ struct PgDatabase { int pool_mode; /* pool mode for this database */ int max_db_connections; /* max server connections between all pools */ char *connect_query; /* startup commands to send to server after connect */ + char *gssapi_spn; /* GSSAPI SPN (Service Principal Name) */ struct PktBuf *startup_params; /* partial StartupMessage (without user) be sent to server */ const char *dbname; /* server-side name, pointer to inside startup_msg */ diff --git a/include/sbuf.h b/include/sbuf.h index 85f92796f109..bc35b19b73ee 100644 --- a/include/sbuf.h +++ b/include/sbuf.h @@ -140,7 +140,7 @@ extern int client_accept_sslmode; */ extern int server_connect_sslmode; -bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) _MUSTCHECK; +bool sbuf_gssenc_connect(SBuf *sbuf, char *gssapi_spn) _MUSTCHECK; bool sbuf_tls_setup(void); bool sbuf_tls_accept(SBuf *sbuf) _MUSTCHECK; bool sbuf_tls_connect(SBuf *sbuf, const char *hostname) _MUSTCHECK; diff --git a/src/janitor.c b/src/janitor.c index d3e03e2ef9e7..2bb8b376e771 100644 --- a/src/janitor.c +++ b/src/janitor.c @@ -709,6 +709,7 @@ void kill_database(PgDatabase *db) if (db->forced_user) slab_free(user_cache, db->forced_user); free(db->connect_query); + free(db->gssapi_spn); if (db->inactive_time) { statlist_remove(&autodatabase_idle_list, &db->head); } else { diff --git a/src/loader.c b/src/loader.c index 82a3a05ad8d6..e2ed1468b9da 100644 --- a/src/loader.c +++ b/src/loader.c @@ -172,6 +172,7 @@ bool parse_database(void *base, const char *name, const char *connstr) char *datestyle = NULL; char *timezone = NULL; char *connect_query = NULL; + char *gssapi_spn = NULL; char *appname = NULL; cv.value_p = &pool_mode; @@ -247,6 +248,12 @@ bool parse_database(void *base, const char *name, const char *connstr) log_error("out of memory"); goto fail; } + } else if (strcmp("gssapi_spn", key) == 0) { + gssapi_spn = strdup(val); + if (!gssapi_spn) { + log_error("out of memory"); + goto fail; + } } else if (strcmp("application_name", key) == 0) { appname = val; } else { @@ -286,6 +293,9 @@ bool parse_database(void *base, const char *name, const char *connstr) } else if (!!connect_query != !!db->connect_query || (connect_query && strcmp(connect_query, db->connect_query) != 0)) { changed = true; + } else if (!!gssapi_spn != !!db->gssapi_spn + || (gssapi_spn && strcmp(gssapi_spn, db->gssapi_spn) != 0)) { + changed = true; } if (changed) tag_database_dirty(db); @@ -301,6 +311,8 @@ bool parse_database(void *base, const char *name, const char *connstr) db->max_db_connections = max_db_connections; free(db->connect_query); db->connect_query = connect_query; + free(db->gssapi_spn); + db->gssapi_spn = gssapi_spn; if (db->startup_params) { msg = db->startup_params; diff --git a/src/sbuf.c b/src/sbuf.c index 33f8c9e77a94..8660c04effee 100644 --- a/src/sbuf.c +++ b/src/sbuf.c @@ -1334,32 +1334,6 @@ static int gssenc_sbufio_close(struct SBuf *sbuf) return 0; } -/* -static ssize_t gssenc_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len) -{ - gss_buffer_desc recv_buf = GSS_C_EMPTY_BUFFER; - gss_buffer_desc unwrap_buf = GSS_C_EMPTY_BUFFER; - int conf_state, ret, token_flags; - OM_uint32 maj, min; - socket_set_nonblocking(sbuf_socket(sbuf), 0); - maj = 0; - min = 0; - log_noise("gssenc_sbufio_recv start"); - ret = recv_token(sbuf->sock, &token_flags, &recv_buf); - if (ret < 0) - return -1; - log_noise("gssenc_sbufio_recv token received"); - maj = gss_unwrap(&min, sbuf->gss, &recv_buf, &unwrap_buf, &conf_state, (gss_qop_t *) NULL); - if (GSS_ERROR(maj)) { - log_warning("gssenc_sbufio_recv - gss_wrap() error major 0x%x minor 0x%x\n", maj, min); - return -1; - } - memcpy(dst, unwrap_buf.value, unwrap_buf.length); - log_noise("gssenc_sbufio_recv end %d", (int) unwrap_buf.length); - return unwrap_buf.length; -} -*/ - static ssize_t gssenc_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len) { ssize_t out = 0; @@ -1385,29 +1359,6 @@ static ssize_t gssenc_sbufio_recv(struct SBuf *sbuf, void *dst, size_t len) return -1; } -/*static ssize_t gssenc_sbufio_send(struct SBuf *sbuf, const void *data, size_t len) -{ - gss_buffer_desc in_buf, out_buf = GSS_C_EMPTY_BUFFER; - OM_uint32 maj, min; - int ret, state; - socket_set_nonblocking(sbuf_socket(sbuf), 0); - log_noise("gssenc_sbufio_send start"); - in_buf.length = len; - in_buf.value = (char *) data; - out_buf.value = NULL; - out_buf.length = 0; - maj = gss_wrap(&min, sbuf->gss, 1, GSS_C_QOP_DEFAULT, &in_buf, &state, &out_buf); - if (GSS_ERROR(maj)) { - log_noise("gssenc_sbufio_send - gss_wrap() error major 0x%x minor 0x%x\n", maj, min); - return -1; - } - ret = send_token(sbuf->sock, 0, &out_buf); - if (ret < 0) - log_error("gssenc_sbufio_send ret %d\n", ret); - log_noise("gssenc_sbufio_send end %d\n", (int) len); - return (ssize_t) len; -}*/ - static ssize_t gssenc_sbufio_send(struct SBuf *sbuf, const void *data, size_t len) { ssize_t out; @@ -2187,7 +2138,7 @@ recv_token(int s, int * flags, gss_buffer_t tok) * Connect to remote GSS Enc host. */ -bool sbuf_gssenc_connect(SBuf *conn, const char *hostname) +bool sbuf_gssenc_connect(SBuf *conn, char *gssapi_spn) { int initiator_established = 0, ret; gss_ctx_id_t ctx = GSS_C_NO_CONTEXT; @@ -2215,7 +2166,7 @@ bool sbuf_gssenc_connect(SBuf *conn, const char *hostname) } /* Applications should set target_name to a real value. */ - name_buf.value = "postgres/kerberized-postgres@EXAMPLE.COM"; + name_buf.value = gssapi_spn; name_buf.length = strlen(name_buf.value); major = gss_import_name(&minor, &name_buf, GSS_KRB5_NT_PRINCIPAL_NAME, &target_name); @@ -2335,5 +2286,5 @@ static bool handle_gssenc_handshake(SBuf *sbuf) //bool sbuf_tls_setup(void) { return true; } //bool sbuf_tls_accept(SBuf *sbuf) { return false; } -bool sbuf_gssenc_connect(SBuf *sbuf, const char *hostname) { return false; } +bool sbuf_gssenc_connect(SBuf *sbuf, char *gssapi_spn) { return false; } #endif \ No newline at end of file diff --git a/src/server.c b/src/server.c index 5a1c1a3b95ff..ba92151706ba 100644 --- a/src/server.c +++ b/src/server.c @@ -544,7 +544,7 @@ static bool handle_gssencchar(PgSocket *server, struct MBuf *data) if (gchar == 'G') { slog_noise(server, "launching gssenc"); - ok = sbuf_gssenc_connect(&server->sbuf, server->pool->db->host); + ok = sbuf_gssenc_connect(&server->sbuf, server->pool->db->gssapi_spn); // TODO: allow refusal // } else if (server_connect_sslmode >= GSSENCMODE_REQUIRE) { // disconnect_server(server, false, "server refused GSSEnc"); From f38bcb97fa4ee713bf8cd172bc1553b489ac81a1 Mon Sep 17 00:00:00 2001 From: coryastronomer <107139210+coryastronomer@users.noreply.github.com> Date: Wed, 10 Aug 2022 18:20:54 -0500 Subject: [PATCH 12/16] updated code to add server_gssencmode support --- configure.ac | 5 ++++- doc/config.md | 2 ++ etc/pgbouncer.ini | 7 +++++++ include/bouncer.h | 12 +++++++----- include/sbuf.h | 9 +++++++-- src/admin.c | 6 ++++++ src/main.c | 14 +++++++++----- src/sbuf.c | 26 +++++++++++++++++--------- src/server.c | 9 ++++----- 9 files changed, 63 insertions(+), 27 deletions(-) diff --git a/configure.ac b/configure.ac index 24e08e91aef7..4421748284b8 100644 --- a/configure.ac +++ b/configure.ac @@ -94,7 +94,7 @@ if test "$with_systemd" = yes; then fi dnl Check for server GSSAPI Encryption support -server_gssenc_support=no +server_gssenc_support=yes AC_ARG_WITH(server-gssenc, AC_HELP_STRING([--with-server-gssenc], [build with server GSSAPI Encryption support]), [ GSS= @@ -105,6 +105,9 @@ AC_ARG_WITH(server-gssenc, if test x"${have_gss_header}" != x -a x"${have_libgss}" != x; then server_gssenc_support=yes AC_DEFINE(HAVE_SERVER_GSSENC, 1, [Server GSSAPI Encryption support]) + else + server_gssenc_support=no + AC_DEFINE(HAVE_SERVER_GSSENC, 0, [Server GSSAPI Encryption support]) fi fi ], []) diff --git a/doc/config.md b/doc/config.md index 18c4ce68886d..2a7182761ef1 100644 --- a/doc/config.md +++ b/doc/config.md @@ -737,6 +737,8 @@ version 1.3 connections. Default: `fast` +### server_gssencmode + ## Dangerous timeouts diff --git a/etc/pgbouncer.ini b/etc/pgbouncer.ini index 4aadcb11a8e8..300cf146ffab 100644 --- a/etc/pgbouncer.ini +++ b/etc/pgbouncer.ini @@ -107,6 +107,13 @@ listen_port = 6432 ;; fast, normal, secure, legacy, ;server_tls_ciphers = fast +;;; +;;; GSSAPI settings for connecting to backend databases +;;; + +;; disable, allow, require +;server_gssencmode = disable + ;;; ;;; Authentication settings ;;; diff --git a/include/bouncer.h b/include/bouncer.h index 67080e70d75c..9a1e9c736b49 100644 --- a/include/bouncer.h +++ b/include/bouncer.h @@ -88,11 +88,10 @@ enum SSLMode { SSLMODE_VERIFY_FULL }; -enum ServerGSSEncMode { - SERVER_GSSENCMODE_DISABLED, - SERVER_GSSENCMODE_ALLOW, - SERVER_GSSENCMODE_PREFER, - SERVER_GSSENCMODE_REQUIRE +enum GSSEncMode { + GSSENCMODE_DISABLE, + GSSENCMODE_PREFER, + GSSENCMODE_REQUIRE }; #define is_server_socket(sk) ((sk)->state >= SV_FREE) @@ -574,6 +573,9 @@ extern char *cf_server_tls_cert_file; extern char *cf_server_tls_key_file; extern char *cf_server_tls_ciphers; +extern int cf_server_gssencmode; + + extern const struct CfLookup pool_mode_map[]; extern usec_t g_suspend_start; diff --git a/include/sbuf.h b/include/sbuf.h index bc35b19b73ee..b32a97559e20 100644 --- a/include/sbuf.h +++ b/include/sbuf.h @@ -43,7 +43,7 @@ typedef enum { */ #define SBUF_SMALL_PKT 64 -#ifdef HAVE_SERVER_GSSENC +#ifdef HAVE_GSSAPI_H #define GSSENC_WANT_POLLOUT -3 #define GSSENC_WANT_POLLIN -2 @@ -98,7 +98,7 @@ struct SBuf { const SBufIO *ops; /* normal vs. TLS vs. GSS */ struct tls *tls; /* TLS context */ struct gss_ctx_id_struct *gss; -#ifdef HAVE_SERVER_GSSENC +#ifdef HAVE_GSSAPI_H char *gss_SendBuffer; /* Encrypted data waiting to be sent */ int gss_SendLength; /* End of data available in gss_SendBuffer */ int gss_SendNext; /* Next index to send a byte from @@ -135,16 +135,21 @@ bool sbuf_connect(SBuf *sbuf, const struct sockaddr *sa, socklen_t sa_len, time_ * usually you should use this variable over cf_client_tls_sslmode. */ extern int client_accept_sslmode; + /* * Same as client_accept_sslmode, but for server connections. */ extern int server_connect_sslmode; +extern int server_connect_gssencmode; + bool sbuf_gssenc_connect(SBuf *sbuf, char *gssapi_spn) _MUSTCHECK; bool sbuf_tls_setup(void); bool sbuf_tls_accept(SBuf *sbuf) _MUSTCHECK; bool sbuf_tls_connect(SBuf *sbuf, const char *hostname) _MUSTCHECK; +bool sbuf_gssenc_setup(void); + bool sbuf_pause(SBuf *sbuf) _MUSTCHECK; void sbuf_continue(SBuf *sbuf); bool sbuf_close(SBuf *sbuf) _MUSTCHECK; diff --git a/src/admin.c b/src/admin.c index 27f0c50c6b73..9a016a369999 100644 --- a/src/admin.c +++ b/src/admin.c @@ -242,6 +242,10 @@ static bool admin_set(PgSocket *admin, const char *key, const char *val) if (!sbuf_tls_setup()) pktbuf_write_Notice(buf, "TLS settings could not be applied, still using old configuration"); } + if (strstr(key, "_gss") != NULL) { + if (!sbuf_gssenc_setup()) + pktbuf_write_Notice(buf, "GSSENC settings could not be applied, still using old configuration") + } snprintf(tmp, sizeof(tmp), "SET %s=%s", key, val); return admin_flush(admin, buf, tmp); } else { @@ -988,6 +992,8 @@ static bool admin_cmd_reload(PgSocket *admin, const char *arg) load_config(); if (!sbuf_tls_setup()) log_error("TLS configuration could not be reloaded, keeping old configuration"); + if (!sbuf_gssenc_setup()) + log_error("GSSENC configuration could not be reloaded, keeping old configuration"); return admin_ready(admin, "RELOAD"); } diff --git a/src/main.c b/src/main.c index 8edd41eba420..fcc0105bfddb 100644 --- a/src/main.c +++ b/src/main.c @@ -225,10 +225,9 @@ const struct CfLookup sslmode_map[] = { }; const struct CfLookup gssencmode_map[] = { - { "disable", SERVER_GSSENCMODE_DISABLED }, - { "allow", SERVER_GSSENCMODE_ALLOW }, - { "prefer", SERVER_GSSENCMODE_PREFER }, - { "require", SERVER_GSSENCMODE_REQUIRE }, + { "disable", GSSENCMODE_DISABLE }, + { "prefer", GSSENCMODE_PREFER }, + { "require", GSSENCMODE_REQUIRE }, { NULL } }; @@ -290,7 +289,7 @@ CF_ABS("server_check_query", CF_STR, cf_server_check_query, 0, "select 1"), CF_ABS("server_connect_timeout", CF_TIME_USEC, cf_server_connect_timeout, 0, "15"), CF_ABS("server_fast_close", CF_INT, cf_server_fast_close, 0, "0"), CF_ABS("server_idle_timeout", CF_TIME_USEC, cf_server_idle_timeout, 0, "600"), -CF_ABS("server_gssencmode", CF_LOOKUP(gssencmode_map), cf_server_gssencmode, 0, "disable"), +CF_ABS("server_gssencmode", CF_LOOKUP(gssencmode_map), cf_server_gssencmode, 0, "prefer"), /* libpq default */ CF_ABS("server_lifetime", CF_TIME_USEC, cf_server_lifetime, 0, "3600"), CF_ABS("server_login_retry", CF_TIME_USEC, cf_server_login_retry, 0, "15"), CF_ABS("server_reset_query", CF_STR, cf_server_reset_query, 0, "DISCARD ALL"), @@ -519,6 +518,8 @@ static void handle_sighup(int sock, short flags, void *arg) load_config(); if (!sbuf_tls_setup()) log_error("TLS configuration could not be reloaded, keeping old configuration"); + if (!sbuf_gssenc_setup()) + log_error("GSSENC configuration could not be reloaded, keeping old configuration"); sd_notify(0, "READY=1"); } #endif @@ -954,6 +955,9 @@ int main(int argc, char *argv[]) if (!sbuf_tls_setup()) die("TLS setup failed"); + if (!sbuf_gssenc_setup()) + die("GSSENC setup failed"); + /* prefer cmdline over config for username */ if (arg_username) { free(cf_username); diff --git a/src/sbuf.c b/src/sbuf.c index 8660c04effee..24332210a38d 100644 --- a/src/sbuf.c +++ b/src/sbuf.c @@ -51,7 +51,7 @@ enum TLSState { SBUF_TLS_OK, }; -#ifdef HAVE_SERVER_GSSENC +#ifdef HAVE_GSSAPI_H enum GSSEncState { SBUF_GSSENC_NONE, SBUF_GSSENC_DO_HANDSHAKE, @@ -91,7 +91,7 @@ static bool sbuf_call_proto(SBuf *sbuf, int event) /* _MUSTCHECK */; static bool sbuf_actual_recv(SBuf *sbuf, size_t len) _MUSTCHECK; static bool sbuf_after_connect_check(SBuf *sbuf) _MUSTCHECK; static bool handle_tls_handshake(SBuf *sbuf) /* _MUSTCHECK */; -#ifdef HAVE_SERVER_GSSENC +#ifdef HAVE_GSSAPI_H static bool handle_gssenc_handshake(SBuf *sbuf) /* _MUSTCHECK */; #endif @@ -119,7 +119,7 @@ static void sbuf_tls_handshake_cb(evutil_socket_t fd, short flags, void *_sbuf); #endif /* I/O over GSS Enc */ -#ifdef HAVE_SERVER_GSSENC +#ifdef HAVE_GSSAPI_H #define Min(x, y) ((x) < (y) ? (x) : (y)) static ssize_t pg_GSS_write(SBuf *conn, const void *ptr, size_t len); static ssize_t pqsecure_raw_write(SBuf *conn, const void *ptr, size_t len); @@ -812,7 +812,7 @@ static void sbuf_main_loop(SBuf *sbuf, bool skip_recv) sbuf->pkt_action = SBUF_TLS_IN_HANDSHAKE; handle_tls_handshake(sbuf); } -#ifdef HAVE_SERVER_GSSENC +#ifdef HAVE_GSSAPI_H if (sbuf->gssenc_state == SBUF_GSSENC_DO_HANDSHAKE) { sbuf->pkt_action = SBUF_GSSENC_IN_HANDSHAKE; handle_gssenc_handshake(sbuf); @@ -1317,7 +1317,16 @@ static bool handle_tls_handshake(SBuf *sbuf) * Server GSS Encryption support. */ -#ifdef HAVE_SERVER_GSSENC +#ifdef HAVE_GSSAPI_H + +int server_connect_gssencmode; + +bool sbuf_gssenc_setup(void) +{ + server_connect_gssencmode = cf_server_gssencmode; + return true; +} + static int gssenc_sbufio_close(struct SBuf *sbuf) { log_noise("gss_close"); @@ -2200,7 +2209,6 @@ bool sbuf_gssenc_connect(SBuf *conn, char *gssapi_spn) * (GSS_S_CONTINUE_NEEDED is set) or if it is nonempty. */ if ((major & GSS_S_CONTINUE_NEEDED) || output_token.length > 0) { -// ret = send_token(conn->sock, NULL, &output_token); ret = send_token(conn->sock, 0, &output_token); if (ret < 0) goto cleanup; @@ -2267,6 +2275,7 @@ bool sbuf_gssenc_connect(SBuf *conn, char *gssapi_spn) static bool handle_gssenc_handshake(SBuf *sbuf) { sbuf->gssenc_state = SBUF_GSSENC_OK; +/* TODO: async for connection */ // sbuf_use_callback_once(sbuf, EV_READ, sbuf_gssenc_handshake_cb); // sbuf_use_callback_once(sbuf, EV_WRITE, sbuf_gssenc_handshake_cb); sbuf_call_proto(sbuf, SBUF_EV_GSSENC_READY); @@ -2281,10 +2290,9 @@ static bool handle_gssenc_handshake(SBuf *sbuf) // sbuf_call_proto(sbuf, SBUF_EV_RECV_FAILED); //} #else -//int client_accept_sslmode = SSLMODE_DISABLED; -//int server_connect_sslmode = SSLMODE_DISABLED; +int server_connect_gssencmode = GSSENCMODE_DISABLE; -//bool sbuf_tls_setup(void) { return true; } +bool sbuf_gssenc_setup(void) { return true; } //bool sbuf_tls_accept(SBuf *sbuf) { return false; } bool sbuf_gssenc_connect(SBuf *sbuf, char *gssapi_spn) { return false; } #endif \ No newline at end of file diff --git a/src/server.c b/src/server.c index ba92151706ba..f80554a1f160 100644 --- a/src/server.c +++ b/src/server.c @@ -462,7 +462,7 @@ static bool handle_connect(PgSocket *server) res = send_sslreq_packet(server); if (res) server->wait_sslchar = true; - } else if (!is_unix) { // TODO: make it work like SSLMODE_ENABLED above + } else if (server_connect_gssencmode > GSSENCMODE_DISABLE && !is_unix) { // TODO: make it work like SSLMODE_ENABLED above slog_noise(server, "P: GSSEnc request"); res = send_gssencreq_packet(server); if (res) @@ -545,10 +545,9 @@ static bool handle_gssencchar(PgSocket *server, struct MBuf *data) if (gchar == 'G') { slog_noise(server, "launching gssenc"); ok = sbuf_gssenc_connect(&server->sbuf, server->pool->db->gssapi_spn); -// TODO: allow refusal -// } else if (server_connect_sslmode >= GSSENCMODE_REQUIRE) { -// disconnect_server(server, false, "server refused GSSEnc"); -// return false; + } else if (server_connect_gssencmode == GSSENCMODE_REQUIRE) { + disconnect_server(server, false, "server refused GSSEnc"); + return false; } else { /* proceed with non-TLS connection */ ok = send_startup_packet(server); From 249c2a68b703aa642922b845d6e4a2a1dacd9cec Mon Sep 17 00:00:00 2001 From: coryastronomer <107139210+coryastronomer@users.noreply.github.com> Date: Sat, 20 Aug 2022 00:49:15 -0500 Subject: [PATCH 13/16] Added one gssenc test (more to come) --- .cirrus.yml | 7 +- test/Makefile | 4 + test/README.md | 6 ++ test/gss/Makefile | 9 ++ test/gss/newkdc.sh | 88 ++++++++++++++++++ test/gss/test.ini | 27 ++++++ test/gss/test.sh | 223 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 363 insertions(+), 1 deletion(-) create mode 100644 test/gss/Makefile create mode 100755 test/gss/newkdc.sh create mode 100644 test/gss/test.ini create mode 100755 test/gss/test.sh diff --git a/.cirrus.yml b/.cirrus.yml index fd38aaf7b461..177849e0254e 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -64,9 +64,14 @@ task: image: debian:oldstable env: PGVERSION: 11 + - container: + image: ubuntu:22.04 + env: + configure_args: '--with-server-gssenc' setup_script: - apt-get update - - apt-get -y install curl gnupg lsb-release + - env DEBIAN_FRONTEND=noninteractive apt-get -y install curl gnupg lsb-release krb5-kdc krb5-admin-server krb5-user libkrb5-dev + - ./test/gss/newkdc.sh - curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - - echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list - apt-get update diff --git a/test/Makefile b/test/Makefile index fecb08b939ea..512ad02e11ea 100644 --- a/test/Makefile +++ b/test/Makefile @@ -38,3 +38,7 @@ check: all ifeq ($(tls_support),yes) $(MAKE) -C ssl check endif + +ifeq ($(server_gssenc_support),yes) + $(MAKE) -C gss check +endif diff --git a/test/README.md b/test/README.md index cdebfea7f297..38316c323e00 100644 --- a/test/README.md +++ b/test/README.md @@ -28,6 +28,12 @@ Various ways to test PgBouncer: This test is run by `make check` if TLS support is enabled. +- `gss/test.sh` + + Tests GSS functionality. Otherwise very similar to `test.sh`. + + This test is run by `make check` if GSS support is enabled. + - `hba_test` Tests hba parsing. Run `make all` to build and `./hba_test` to execute. diff --git a/test/gss/Makefile b/test/gss/Makefile new file mode 100644 index 000000000000..3442a412de07 --- /dev/null +++ b/test/gss/Makefile @@ -0,0 +1,9 @@ +EXTRA_DIST = test.sh Makefile + +SUBLOC = test/gss + +include ../../config.mak +include ../../lib/mk/antimake.mk + +check: all + ./test.sh diff --git a/test/gss/newkdc.sh b/test/gss/newkdc.sh new file mode 100755 index 000000000000..fda88c8ab6e3 --- /dev/null +++ b/test/gss/newkdc.sh @@ -0,0 +1,88 @@ +#! /bin/sh + +# TODO: do not run if /etc/krb5.conf exists, kdc or kadmin is running, or if /var/lib/krb5kdc exists + +# TODO: remove after debugging +apt-get update +env DEBIAN_FRONTEND=noninteractive apt-get -y install curl gnupg lsb-release krb5-kdc krb5-admin-server krb5-user + +cd $(dirname $0) + +REALM=EXAMPLE.COM +SUPPORTED_ENCRYPTION_TYPES=aes256-cts-hmac-sha1-96:normal +KADMIN_PRINCIPAL=kadmin/admin +KADMIN_PASSWORD=51rb0unc3r +KDC_KADMIN_SERVER=$(hostname -f) + +LOGDIR=log +PG_LOG=$LOGDIR/krb.log + +ulimit -c unlimited + +configure_kdc() { + # Assumes packages are installed; krb5-kdc and krb5-admin-server on debian + command -v krb5kdc > /dev/null || { + echo "krb5kdc not found, need kerberos tools in PATH" + exit 0 + } + KADMIN_PRINCIPAL_FULL=$KADMIN_PRINCIPAL@$REALM + cat << EOF > /etc/krb5.conf +[libdefaults] + default_realm = $REALM + rdns = false + +[realms] + $REALM = { + kdc_ports = 88,750 + kadmind_port = 749 + kdc = $KDC_KADMIN_SERVER + admin_server = $KDC_KADMIN_SERVER + } +EOF + + cat << EOF > /etc/krb5kdc/kdc.conf +[realms] + $REALM = { + acl_file = /etc/krb5kdc/kadm5.acl + max_renewable_life = 7d 0h 0m 0s + supported_enctypes = $SUPPORTED_ENCRYPTION_TYPES + default_principal_flags = +preauth + } +EOF + cat << EOF > /etc/krb5kdc/kadm5.acl +$KADMIN_PRINCIPAL_FULL * +EOF + MASTER_PASSWORD=$(tr -cd '[:alnum:]' < /dev/urandom | fold -w30 | head -n1) + # This command also starts the krb5-kdc and krb5-admin-server services + krb5_newrealm <>$PG_LOG 2>&1 +} + +ulimit -c unlimited + +SED_ERE_OP='-E' +case `uname` in +Linux) + SED_ERE_OP='-r' + ;; +esac + +pg_majorversion=$(initdb --version | sed -n $SED_ERE_OP 's/.* ([0-9]+).*/\1/p') +if test $pg_majorversion -ge 10; then + pg_supports_scram=true +else + pg_supports_scram=false +fi + +stopit() { + local pid + if test -f "$1"; then + pid=`head -n1 "$1"` + kill $pid + while kill -0 $pid 2>/dev/null; do sleep 0.1; done + rm -f "$1" + fi +} + +stopit test.pid +stopit pgdata/postmaster.pid + +mkdir -p $LOGDIR +rm -f $BOUNCER_LOG $PG_LOG +rm -rf $PGDATA + +if [ ! -d $PGDATA ]; then + echo "initdb" + mkdir $PGDATA + initdb -A trust --nosync >> $PG_LOG + echo "unix_socket_directories = '/tmp'" >> pgdata/postgresql.conf + echo "port = $PG_PORT" >> pgdata/postgresql.conf + # We need to make the log go to stderr so that the tests can + # check what is being logged. This should be the default, but + # some packagings change the default configuration. + echo "logging_collector = off" >> pgdata/postgresql.conf + echo "log_destination = stderr" >> pgdata/postgresql.conf + echo "log_connections = on" >> pgdata/postgresql.conf + echo "log_disconnections = on" >> pgdata/postgresql.conf + cp pgdata/postgresql.conf pgdata/postgresql.conf.orig + cp pgdata/pg_hba.conf pgdata/pg_hba.conf.orig + cp pgdata/pg_ident.conf pgdata/pg_ident.conf.orig + + cp /krb5.keytab pgdata/krb5.keytab + chmod 600 pgdata/krb5.keytab + + echo '"bouncer" "zzz"' > tmp/userlist.txt + + chmod 600 tmp/userlist.txt +fi + +pgctl start + +echo "createdb" +psql -X -p $PG_PORT -l | grep p0 > /dev/null || { + psql -X -o /dev/null -p $PG_PORT -c "create user bouncer" template1 + createdb -p $PG_PORT p0 + createdb -p $PG_PORT p1 +} + +reconf_bouncer() { + cp test.ini tmp/test.ini + for ln in "$@"; do + echo "$ln" >> tmp/test.ini + done + test -f test.pid && kill `cat test.pid` + sleep 1 + $BOUNCER_EXE -v -v -v -d tmp/test.ini +} + +reconf_pgsql() { + cp pgdata/postgresql.conf.orig pgdata/postgresql.conf + for ln in "$@"; do + echo "$ln" >> pgdata/postgresql.conf + done + pgctl stop + pgctl start + sleep 1 +} + + +# +# fw hacks +# + +# +# util functions +# + +complete() { + test -f $BOUNCER_PID && kill `cat $BOUNCER_PID` >/dev/null 2>&1 + pgctl -m fast stop + rm -f $BOUNCER_PID +} + +die() { + echo $@ + complete + exit 1 +} + +admin() { + psql -X -h /tmp -U pgbouncer -d pgbouncer -c "$@;" || die "Cannot contact bouncer!" +} + +runtest() { + local status + + $BOUNCER_EXE -d $BOUNCER_INI + until psql -X -h /tmp -U pgbouncer -d pgbouncer -c "show version" 2>/dev/null 1>&2; do sleep 0.1; done + + printf "`date` running $1 ... " + eval $1 >$LOGDIR/$1.out 2>&1 + status=$? + + # Detect fatal errors from PgBouncer (which are internal + # errors), but not those from PostgreSQL (which could be + # normal, such as authentication failures) + if grep 'FATAL @' $BOUNCER_LOG >> $LOGDIR/$1.out; then + status=1 + fi + + if [ $status -eq 0 ]; then + echo "ok" + elif [ $status -eq 77 ]; then + echo "skipped" + status=0 + else + echo "FAILED" + cat $LOGDIR/$1.out | sed 's/^/# /' + fi + date >> $LOGDIR/$1.out + + # allow background processing to complete + wait + + stopit test.pid + mv $BOUNCER_LOG $LOGDIR/$1.log + + return $status +} + +psql_pg() { + psql -X -U bouncer -h 127.0.0.1 -p $PG_PORT "$@" +} + +psql_bouncer() { + PGUSER=bouncer PGPASSWORD=zzz psql -X "$@" +} + +test_server_gss() { + reconf_bouncer "server_gssencmode = require" +# reconf_bouncer "server_gssencmode = prefer" +# reconf_bouncer "server_gssencmode = disable" + echo "hostgssenc all all 0.0.0.0/0 gss include_realm=0 krb_realm=EXAMPLE.COM" > pgdata/pg_hba.conf + echo "hostgssenc all all ::/0 gss include_realm=0 krb_realm=EXAMPLE.COM" >> pgdata/pg_hba.conf + reconf_pgsql "krb_server_keyfile = '$PGDATA/krb5.keytab'" + psql_bouncer -q -d p0 -c 'SELECT pid, gss_authenticated, encrypted, principal from pg_stat_gssapi where pid = pg_backend_pid();' | tee tmp/test.tmp1 + grep -Eq 't.*t.*bouncer@EXAMPLE.COM' tmp/test.tmp1 + rc=$? + return $rc +} + +testlist=" +test_server_gss +" +if [ $# -gt 0 ]; then + testlist="$*" +fi + +total_status=0 +for test in $testlist +do + runtest $test + status=$? + if [ $status -ne 0 ]; then + total_status=1 + fi +done + +complete + +exit $total_status + +# vim: sts=0 sw=8 noet nosmarttab: From a0fecf49712cc1ae03732bc627dc5a0a2733298d Mon Sep 17 00:00:00 2001 From: coryastronomer <107139210+coryastronomer@users.noreply.github.com> Date: Fri, 26 Aug 2022 18:16:05 -0500 Subject: [PATCH 14/16] Fix up server_gssencmode=prefer behavior --- .cirrus.yml | 2 +- doc/config.md | 12 ++++++++++++ src/server.c | 23 ++++++++++++++++++++++- test/gss/test.ini | 7 +++---- 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 177849e0254e..b887407649a3 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -150,7 +150,7 @@ task: - image: alpine:latest setup_script: - apk update - - apk add autoconf automake build-base libevent-dev libtool openssl openssl-dev pkgconf postgresql python3 wget + - apk add autoconf automake bash build-base libevent-dev libtool openssl openssl-dev pkgconf postgresql python3 wget - wget -O /tmp/pandoc.tar.gz https://github.com/jgm/pandoc/releases/download/2.10.1/pandoc-2.10.1-linux-amd64.tar.gz - tar xvzf /tmp/pandoc.tar.gz --strip-components 1 -C /usr/local/ - adduser --disabled-password user diff --git a/doc/config.md b/doc/config.md index 2a7182761ef1..0ce640701099 100644 --- a/doc/config.md +++ b/doc/config.md @@ -739,6 +739,18 @@ Default: `fast` ### server_gssencmode +This option determines whether or with what priority a secure GSS TCP/IP connection will be negotiated with the server. There are three modes: + +disable +: only try a non-GSSAPI-encrypted connection + +prefer (default) +: if there are GSSAPI credentials present (i.e., in a credentials cache), first try a GSSAPI-encrypted connection; if that fails or there are no credentials, try a non-GSSAPI-encrypted connection. This is the default when pgbouncer has been compiled with GSSAPI support. + +require +:only try a GSSAPI-encrypted connection + +server_gssencmode is ignored for Unix domain socket communication. If pgbouncer is compiled without GSSAPI support, using the require option will cause an error, while prefer will be accepted but pgbouncer will not actually attempt a GSSAPI-encrypted connection. ## Dangerous timeouts diff --git a/src/server.c b/src/server.c index f80554a1f160..38a2ae1640ac 100644 --- a/src/server.c +++ b/src/server.c @@ -430,6 +430,26 @@ static bool handle_server_work(PgSocket *server, PktHdr *pkt) return true; } +/* + * Check if we can acquire credentials at all (and yield them if so). + */ +static bool pg_GSS_have_cred_cache(gss_cred_id_t *cred_out) +{ + OM_uint32 major, + minor; + gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; + + major = gss_acquire_cred(&minor, GSS_C_NO_NAME, 0, GSS_C_NO_OID_SET, + GSS_C_INITIATE, &cred, NULL, NULL); + if (major != GSS_S_COMPLETE) + { + *cred_out = NULL; + return false; + } + *cred_out = cred; + return true; +} + /* got connection, decide what to do */ static bool handle_connect(PgSocket *server) { @@ -437,6 +457,7 @@ static bool handle_connect(PgSocket *server) PgPool *pool = server->pool; char buf[PGADDR_BUF + 32]; bool is_unix = pga_is_unix(&server->remote_addr); + gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; fill_local_addr(server, sbuf_socket(&server->sbuf), is_unix); @@ -462,7 +483,7 @@ static bool handle_connect(PgSocket *server) res = send_sslreq_packet(server); if (res) server->wait_sslchar = true; - } else if (server_connect_gssencmode > GSSENCMODE_DISABLE && !is_unix) { // TODO: make it work like SSLMODE_ENABLED above + } else if (server_connect_gssencmode > GSSENCMODE_DISABLE && !is_unix && pg_GSS_have_cred_cache(&cred)) { slog_noise(server, "P: GSSEnc request"); res = send_gssencreq_packet(server); if (res) diff --git a/test/gss/test.ini b/test/gss/test.ini index 11b309e4512a..98eefa3f0ac8 100644 --- a/test/gss/test.ini +++ b/test/gss/test.ini @@ -1,6 +1,6 @@ [databases] -p0 = port=6666 host=localhost dbname=p0 user=bouncer pool_size=2 gssapi_spn=postgres/ip-172-31-19-107.us-east-2.compute.internal@EXAMPLE.COM -p1 = port=6666 host=localhost dbname=p1 user=bouncer gssapi_spn=postgres/ip-172-31-19-107.us-east-2.compute.internal@EXAMPLE.COM +p0 = port=6666 host=localhost dbname=p0 user=bouncer pool_size=2 gssapi_spn=postgres/FQDN@EXAMPLE.COM +p1 = port=6666 host=localhost dbname=p1 user=bouncer gssapi_spn=postgres/FQDN@EXAMPLE.COM [pgbouncer] logfile = test.log @@ -23,5 +23,4 @@ server_lifetime = 120 server_idle_timeout = 60 pkt_buf = 16384 -verbose=999 -server_gssencmode = require +server_gssenc_mode = require From 4691a07823d1d5eb032358f16a196a6234e9d82a Mon Sep 17 00:00:00 2001 From: coryastronomer <107139210+coryastronomer@users.noreply.github.com> Date: Fri, 26 Aug 2022 18:31:23 -0500 Subject: [PATCH 15/16] ifdef for gssencmode --- src/server.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/server.c b/src/server.c index 38a2ae1640ac..d145193e9b46 100644 --- a/src/server.c +++ b/src/server.c @@ -430,6 +430,7 @@ static bool handle_server_work(PgSocket *server, PktHdr *pkt) return true; } +#ifdef HAVE_GSSAPI_H /* * Check if we can acquire credentials at all (and yield them if so). */ @@ -449,6 +450,7 @@ static bool pg_GSS_have_cred_cache(gss_cred_id_t *cred_out) *cred_out = cred; return true; } +#endif /* got connection, decide what to do */ static bool handle_connect(PgSocket *server) @@ -483,11 +485,13 @@ static bool handle_connect(PgSocket *server) res = send_sslreq_packet(server); if (res) server->wait_sslchar = true; +#ifdef HAVE_GSSAPI_H } else if (server_connect_gssencmode > GSSENCMODE_DISABLE && !is_unix && pg_GSS_have_cred_cache(&cred)) { slog_noise(server, "P: GSSEnc request"); res = send_gssencreq_packet(server); if (res) server->wait_gssencchar = true; +#endif } else { slog_noise(server, "P: startup"); res = send_startup_packet(server); From 48419cd6de8a6fd5334defe9e6f8372a66588b3d Mon Sep 17 00:00:00 2001 From: coryastronomer <107139210+coryastronomer@users.noreply.github.com> Date: Fri, 26 Aug 2022 18:37:10 -0500 Subject: [PATCH 16/16] ifdef for "gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;" --- src/server.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server.c b/src/server.c index d145193e9b46..130ae3c9d702 100644 --- a/src/server.c +++ b/src/server.c @@ -459,7 +459,9 @@ static bool handle_connect(PgSocket *server) PgPool *pool = server->pool; char buf[PGADDR_BUF + 32]; bool is_unix = pga_is_unix(&server->remote_addr); +#ifdef HAVE_GSSAPI_H gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; +#endif fill_local_addr(server, sbuf_socket(&server->sbuf), is_unix);